Node.js cookbook · Beginner
Call external APIs with timeouts and retries in Node.js
Make outbound HTTP calls that never hang and recover from transient failures.
Approach
Use the built-in fetch with AbortSignal.timeout() for every call. Retry idempotent requests on network errors, 429 and 5xx with exponential backoff and jitter, respect Retry-After, and cap attempts.
Solution
const sleep = (ms: number) => new Promise((r) => setTimeout(r, ms));
const RETRYABLE = new Set([429, 502, 503, 504]);
export async function fetchJson<T>(url: string, init: RequestInit = {}, { attempts = 3, timeoutMs = 5000 } = {}): Promise<T> {
const method = (init.method ?? "GET").toUpperCase();
const idempotent = ["GET", "HEAD", "PUT", "DELETE"].includes(method);
for (let attempt = 1; ; attempt++) {
try {
const res = await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) });
if (res.ok) return (await res.json()) as T;
if (!idempotent || !RETRYABLE.has(res.status) || attempt >= attempts) {
throw new Error(`HTTP ${res.status} from ${url}`);
}
const retryAfter = Number(res.headers.get("retry-after"));
await sleep(retryAfter > 0 ? retryAfter * 1000 : backoff(attempt));
} catch (err) {
const transient = err instanceof TypeError || (err as Error).name === "TimeoutError"; // network error / timeout
if (!idempotent || !transient || attempt >= attempts) throw err;
await sleep(backoff(attempt));
}
}
}
const backoff = (attempt: number) => Math.min(5000, 200 * 2 ** attempt) + Math.random() * 100;
Pitfalls
- No timeout (Node's fetch can wait a very long time).
- Retrying POSTs without idempotency keys.
- Retry storms during outages — add a circuit breaker (
opossum).
Variations
undiciAgent with connection pooling and per-origin limits.kyorgotlibraries with built-in retries.
Further reading: MDN: AbortSignal.timeout()
Reference: https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/timeout_static
More Node.js recipes
- Add health and readiness endpoints to a Node.js service
- Add JWT authentication to an Express API
- Cache expensive lookups in Redis from Node.js
- Centralize error handling in Express 5
- Hash passwords and implement login safely in Node.js
- Implement cursor pagination with PostgreSQL in Node.js
- Offload CPU-heavy work with worker threads
- Process background jobs with BullMQ