axios-retry, fetch-retry, and node-fetch-retry are utilities designed to add automatic retry capabilities to HTTP requests when transient failures occur, such as network timeouts or 503 Service Unavailable errors.
axios-retry is a dedicated plugin for the popular axios HTTP client, offering extensive configuration for retry conditions, delays, and request ID tracking.
fetch-retry is a wrapper around the native fetch API (or node-fetch), allowing developers to add retry logic to the standard fetch interface without switching HTTP clients.
node-fetch-retry was a similar wrapper specifically for the node-fetch library in Node.js environments, but it is now deprecated and should not be used in new projects.
Network requests fail. Sometimes it's a blip in the connection, sometimes the server is overwhelmed. In production apps, simply giving up after one failure is rarely an option. We need retry logic.
The JavaScript ecosystem offers three main contenders for this job: axios-retry, fetch-retry, and the now-defunct node-fetch-retry. While they all solve the same problem, they fit into different architectural stacks. Let's break down how they work, where they shine, and why one of them should be avoided entirely.
The fundamental difference lies in how these tools attach to your HTTP client.
axios-retry acts as an interceptor plugin. It hooks directly into axios's internal request/response cycle. It doesn't wrap the function call; instead, it registers logic that runs automatically whenever axios makes a request. This allows it to access deep internal state, like unique request IDs.
// axios-retry: Attaches via interceptors
import axios from 'axios';
import axiosRetry from 'axios-retry';
axiosRetry(axios, {
retries: 3,
retryCondition: (error) => {
return axiosRetry.isNetworkOrIdempotentRequestError(error);
}
});
// Usage remains standard axios
axios.get('https://api.example.com/data');
fetch-retry acts as a function wrapper. It takes the native fetch function (or node-fetch) and returns a new function that looks and feels exactly like fetch, but with retry logic baked in. You explicitly create this wrapped version and use it instead of the global fetch.
// fetch-retry: Wraps the fetch function
import fetchRetry from 'fetch-retry';
const fetchWithRetry = fetchRetry(fetch);
// Usage uses the wrapped function
fetchWithRetry('https://api.example.com/data', {
retries: 3,
retryDelay: 1000
})
.then(response => response.json());
node-fetch-retry was historically a wrapper specifically for the node-fetch package in Node.js environments. However, this package is deprecated. The maintainers have archived it, and it is no longer receiving updates. Using it introduces security risks and technical debt. You should treat it as obsolete.
// node-fetch-retry: DEPRECATED - Do not use
// import fetchRetry from 'node-fetch-retry';
// β This package is unmaintained. Switch to 'fetch-retry' instead.
When a request fails, you rarely want to retry immediately. Hammering a struggling server can make things worse. All active packages support custom delay strategies, but the implementation differs slightly.
axios-retry provides a retryDelay option that can be a fixed number or a function. The function receives the retry count, letting you implement exponential backoff easily.
// axios-retry: Custom delay function
axiosRetry(axios, {
retryDelay: (retryCount) => {
// Exponential backoff: 1s, 2s, 4s...
return retryCount * 1000;
}
});
fetch-retry offers the same flexibility via its configuration object passed during the wrapper creation or the individual call. It also supports a function for dynamic delays.
// fetch-retry: Custom delay function
const fetchWithRetry = fetchRetry(fetch, {
retryDelay: (attemptIndex) => {
return Math.pow(2, attemptIndex) * 1000; // Exponential backoff
}
});
Both approaches allow you to tailor the timing to your server's recovery characteristics. The syntax is nearly identical, making migration between them (if switching HTTP clients) straightforward regarding delay logic.
Not all errors deserve a retry. A 404 Not Found won't fix itself if you try again. A 503 Service Unavailable might. Defining the "retry condition" is critical.
axios-retry shines here with built-in helpers. It includes isNetworkOrIdempotentRequestError, which automatically handles common cases like network errors or safe HTTP methods (GET, HEAD, OPTIONS). You can also write your own logic based on the error object.
// axios-retry: Built-in helper + custom logic
axiosRetry(axios, {
retryCondition: (error) => {
// Use built-in check OR custom status code check
return axiosRetry.isNetworkOrIdempotentRequestError(error) ||
error.response?.status === 429; // Retry on Rate Limit
}
});
fetch-retry requires you to define the logic explicitly, as it operates on the standard Response or Error objects. It doesn't ship with named helpers, so you check the status or network state directly.
// fetch-retry: Explicit logic
const fetchWithRetry = fetchRetry(fetch, {
retryOn: (attempt, error, response) => {
if (attempt >= 3) return false; // Stop after 3 tries
if (error !== null) return true; // Network error
if (response && response.status === 429) return true; // Rate limited
return false;
}
});
While axios-retry offers a slight convenience with its helper functions, fetch-retry's explicit approach gives you full transparency into exactly what is being checked.
In complex systems, you might need to know if a request is a retry to handle idempotency keys or logging correctly.
axios-retry automatically attaches a retryCount property to the request config and can inject a custom header (like X-Request-ID) that persists across retries. This is vital for backend tracing.
// axios-retry: Automatic request ID injection
axiosRetry(axios, {
retryCondition: (error) => {
const config = error.config;
if (!config) return false;
// Access how many times this specific request has retried
console.log(`Retry attempt: ${config['axios-retry'].retryCount}`);
return true;
},
// Automatically add header to identify the request across retries
onRetry: (retryCount, error, requestConfig) => {
requestConfig.headers['X-Retry-Count'] = retryCount;
}
});
fetch-retry does not have built-in request ID tracking because it wraps the generic fetch API. You must manually manage headers or context if you need to track retry counts across attempts.
// fetch-retry: Manual tracking required
let attempt = 0;
const fetchWithTracking = async (url, options) => {
const fetchWithRetry = fetchRetry(fetch, {
onRetry: (attemptIndex, err, response) => {
// Manually log or modify headers if needed
console.log(`Retrying request, attempt: ${attemptIndex + 1}`);
}
});
return fetchWithRetry(url, options);
};
If your architecture relies heavily on distributed tracing where every retry must carry the same trace ID, axios-retry handles this out of the box with less boilerplate.
Where can you run these?
axios-retry works wherever axios works. Since axios runs in browsers and Node.js, this plugin is universal. However, it ties you to the axios library.
fetch-retry is truly universal. It works in modern browsers (using native fetch) and in Node.js (using native fetch in v18+ or node-fetch for older versions). It is the most flexible choice for isomorphic codebases that want to avoid axios.
node-fetch-retry was limited to Node.js only. Given its deprecation, it has no place in modern cross-platform development.
| Feature | axios-retry | fetch-retry | node-fetch-retry |
|---|---|---|---|
| Base Client | Axios | Native Fetch / node-fetch | node-fetch (Legacy) |
| Integration | Interceptor Plugin | Function Wrapper | Function Wrapper |
| Status | β Active | β Active | β Deprecated |
| Retry Helpers | Built-in (isNetwork...) | Manual Implementation | Manual Implementation |
| Request Tracking | Automatic (Config/Headers) | Manual | Manual |
| Bundle Impact | Requires Axios | Lightweight (No Axios) | Lightweight (Legacy) |
Choosing the right retry tool depends entirely on your HTTP client strategy.
If your team is already committed to axios, axios-retry is the natural choice. Its deep integration, automatic request ID handling, and helper functions reduce boilerplate and make complex retry scenarios easier to manage. It feels like a native part of the axios ecosystem.
If you prefer standard web APIs and want to keep your bundle size lean, fetch-retry is the superior modern option. It brings robust retry logic to the native fetch without forcing you to adopt a third-party HTTP client. It works everywhere and future-proofs your code as Node.js and browsers converge on native fetch.
Avoid node-fetch-retry completely. It is a relic of a time before native fetch existed in Node.js. Continuing to use it offers no benefits over fetch-retry and introduces significant maintenance risks.
Final Thought: Network instability is a fact of life. Whether you choose the deep features of axios-retry or the standards-based approach of fetch-retry, adding retry logic is a non-negotiable step for building resilient frontend applications. Just make sure you pick the tool that matches your stackβand leave the deprecated ones behind.
Choose axios-retry if your project already relies on axios as its primary HTTP client. It offers the deepest integration, including features like tracking retry counts via request IDs and highly customizable delay algorithms. It is the best choice for complex enterprise applications needing fine-grained control over retry behavior within the axios ecosystem.
Choose fetch-retry if you prefer using the native fetch API or need a lightweight solution that works in both browsers and Node.js without adding the bulk of axios. It is ideal for modern projects aiming to reduce dependencies while still requiring robust error handling for network flakiness.
Do NOT choose node-fetch-retry for new projects. This package is deprecated and no longer maintained. Its functionality has been superseded by fetch-retry, which now supports node-fetch and the native fetch API universally. Migrating to fetch-retry ensures long-term stability and security.
Axios plugin that intercepts failed requests and retries them whenever possible.
npm install axios-retry
// CommonJS
// const axiosRetry = require('axios-retry').default;
// ES6
import axiosRetry from 'axios-retry';
axiosRetry(axios, { retries: 3 });
axios.get('http://example.com/test') // The first request fails and the second returns 'ok'
.then(result => {
result.data; // 'ok'
});
// Exponential back-off retry delay between requests
axiosRetry(axios, { retryDelay: axiosRetry.exponentialDelay });
// Liner retry delay between requests
axiosRetry(axios, { retryDelay: axiosRetry.linearDelay() });
// Custom retry delay
axiosRetry(axios, { retryDelay: (retryCount) => {
return retryCount * 1000;
}});
// Works with custom axios instances
const client = axios.create({ baseURL: 'http://example.com' });
axiosRetry(client, { retries: 3 });
client.get('/test') // The first request fails and the second returns 'ok'
.then(result => {
result.data; // 'ok'
});
// Allows request-specific configuration
client
.get('/test', {
'axios-retry': {
retries: 0
}
})
.catch(error => { // The first request fails
error !== undefined
});
Note: Unless shouldResetTimeout is set, the plugin interprets the request timeout as a global value, so it is not used for each retry but for the whole request lifecycle.
| Name | Type | Default | Description |
|---|---|---|---|
| retries | Number | 3 | The number of times to retry before failing. 1 = One retry after first failure |
| retryCondition | Function | isNetworkOrIdempotentRequestError | A callback to further control if a request should be retried. By default, it retries if it is a network error or a 5xx error on an idempotent request (GET, HEAD, OPTIONS, PUT or DELETE). |
| shouldResetTimeout | Boolean | false | Defines if the timeout should be reset between retries |
| retryDelay | Function | function noDelay() { return 0; } | A callback to further control the delay in milliseconds between retried requests. By default there is no delay between retries. Another option is exponentialDelay (Exponential Backoff) or linearDelay. The function is passed retryCount and error. |
| onRetry | Function | function onRetry(retryCount, error, requestConfig) { return; } | A callback to notify when a retry is about to occur. Useful for tracing and you can any async process for example refresh a token on 401. By default nothing will occur. The function is passed retryCount, error, and requestConfig. |
| onMaxRetryTimesExceeded | Function | function onMaxRetryTimesExceeded(error, retryCount) { return; } | After all the retries are failed, this callback will be called with the last error before throwing the error. |
| validateResponse | Function | null | null | A callback to define whether a response should be resolved or rejected. If null is passed, it will fallback to the axios default (only 2xx status codes are resolved). |
Clone the repository and execute:
npm test
git clone https://github.com/softonic/axios-retry.gitgit checkout -b feature/my-new-featuregit commit -am 'Added some feature'npm run buildgit push origin my-new-feature