Build a Rock-Solid API with Retry & Timeout in 10 Minutes ⏱️



This content originally appeared on DEV Community and was authored by Bouhadjila Hamza

🚀 Retry, Abort, and Batch like a Pro — Build a Resilient Express API with oh-no-again.js 😬

Are you tired of flaky APIs ruining your vibe? Let’s fix that with a fun Express.js project using the free and awesome Rick and Morty API and a utility package built from pain and frustration: oh-no-again.

✅ Retry failed requests\
✅ Batch concurrent fetches\
✅ Timeout and abort support\
✅ All powered by native fetch

🌟 What Are We Building?

An Express.js API endpoint:

GET /characters?ids=1,2,3

It will:

  • Fetch multiple Rick and Morty characters in parallel
  • Retry failed requests automatically with exponential backoff
  • Abort slow requests with a timeout
  • Report success and failure clearly

📆 Step 1: Setup the Project

mkdir rickmorty-batch-api
cd rickmorty-batch-api
npm init -y
npm install express oh-no-again

✍ Step 2: Create the API

Create a file called server.js:

const express = require('express');
const { requestBatcher } = require('oh-no-again');

const app = express();
const PORT = process.env.PORT || 3000;

app.use(express.json());

app.get('/characters', async (req, res) => {
  try {
    const ids = (req.query.ids || '')
      .split(',')
      .map((id) => parseInt(id.trim()))
      .filter(Boolean);

    if (!ids.length) {
      return res
        .status(400)
        .json({ error: 'Please provide character IDs: ?ids=1,2,3' });
    }

    const result = await requestBatcher(
      ids,
      3, // concurrency
      (id) => ({
        url: `https://rickandmortyapi.com/api/character/${id}`,
        method: 'GET',
        headers: { 'Content-Type': 'application/json' },
      }),
      {
        retries: 3,
        delay: 200,
        timeout: 100,
        returnMeta: true,
        hooks: {
          onRetry: (err, attempt) =>
            console.warn(`[RETRY] Attempt ${attempt + 1}: ${err.message}`),
          onFailure: (err) => console.error('[FAILURE]', err.message),
          onSuccess: (res) =>
            console.log('[SUCCESS] Character fetched.', res.name),
        },
      },
    );

    return res.json({ count: result.length, result });
  } catch (err) {
    res.status(500).json({
      error: 'Failed to fetch users',
      message: err.message,
    });
  }
});

app.listen(PORT, () => {
  console.log(`Server is running on port ${PORT}`);
});

📊 Step 3: Test It

Start the server:

node server.js

Then visit:

http://localhost:3000/characters?ids=1,2,3,9999

You’ll get a result like:

{
  "count": 4,
  "result": [
    {
      "item": 1,
      "result": { "id": 1, "name": "Rick Sanchez", ... },
      "success": true
    },
    {
      "item": 2,
      "result": { "id": 2, "name": "Morty Smith", ... },
      "success": true
    },
    ...
    {
      "item": 9999,
      "error": "Fetch failed with 404 Not Found. Body: {}",
      "success": false
    }
  ]
}

🔍 Under the Hood — What is oh-no-again?

oh-no-again is a lightweight utility that gives you:

Feature Description
retryHelper Retries any async function with AbortSignal support
requestBatcher Handles concurrency, batching, retries and metadata
Built-in fetch Uses native fetch, no axios dependency
Hook system onRetry, onFailure, onSuccess, onBatchComplete

🔗 API Recap

requestBatcher(data, concurrency, taskFn, options)

taskFn: (item) => {
  url: string,
  method?: string,
  headers?: Record<string, string>,
  body?: any
}

Options include:

  • retries, delay, timeout
  • returnMeta: true returns { success, result, item }
  • failFast: true throws error immediately on any failure
  • hooks: lifecycle tracking

🤔 Final Thoughts

This mini project demonstrates how retry logic and batching can be elegant, safe, and fun.\
No more callback hell. No more flaky APIs ruining your night.

And thanks to the open Rick and Morty API — we had a blast doing it.

📂 Resources

🙌 Built by Bouhadjila Hamza

Because we’ve all said: “oh no… not again.”


This content originally appeared on DEV Community and was authored by Bouhadjila Hamza