How to Retry Axios Requests by Error Code

Handling transient network failures and rate limits is essential for building resilient applications. This article explains how to implement custom retry logic in the Axios HTTP client based on specific HTTP status codes and network error codes, covering both the popular axios-retry plugin and a vanilla Axios interceptor approach.


Method 1: Using the axios-retry Plugin

The simplest and most robust way to add retry functionality is using the axios-retry library. It provides a built-in retryCondition hook to inspect errors and decide whether to retry.

1. Installation

npm install axios axios-retry

2. Implementation

const axios = require('axios');
const axiosRetry = require('axios-retry');

const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
});

// Define specific status codes to retry
const RETRYABLE_STATUS_CODES = [429, 502, 503, 504];

// Define specific network error codes to retry
const RETRYABLE_NETWORK_ERRORS = ['ECONNABORTED', 'ETIMEDOUT', 'ECONNRESET'];

axiosRetry(apiClient, {
  retries: 3,
  retryDelay: axiosRetry.exponentialDelay,
  retryCondition: (error) => {
    // 1. Check HTTP response status codes
    if (error.response && RETRYABLE_STATUS_CODES.includes(error.response.status)) {
      return true;
    }

    // 2. Check Node.js / network error codes
    if (error.code && RETRYABLE_NETWORK_ERRORS.includes(error.code)) {
      return true;
    }

    // Do not retry on client errors like 400, 401, 403, 404
    return false;
  },
});

module.exports = apiClient;

Method 2: Native Axios Response Interceptors

If you prefer not to add external dependencies, you can implement retry logic directly inside an Axios response interceptor by tracking retry counts in the request configuration.

const axios = require('axios');

const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  timeout: 5000,
});

const RETRYABLE_STATUS_CODES = [429, 502, 503, 504];
const MAX_RETRIES = 3;
const BASE_DELAY_MS = 1000;

apiClient.interceptors.response.use(
  (response) => response,
  async (error) => {
    const config = error.config;

    // Avoid retrying if no config exists
    if (!config) {
      return Promise.reject(error);
    }

    // Initialize retry state
    config.__retryCount = config.__retryCount || 0;

    // Define custom condition
    const isRetryableStatus = error.response && RETRYABLE_STATUS_CODES.includes(error.response.status);
    const isTimeout = error.code === 'ECONNABORTED';
    const shouldRetry = (isRetryableStatus || isTimeout) && config.__retryCount < MAX_RETRIES;

    if (shouldRetry) {
      config.__retryCount += 1;

      // Calculate exponential backoff delay
      const delay = BASE_DELAY_MS * Math.pow(2, config.__retryCount - 1);
      await new Promise((resolve) => setTimeout(resolve, delay));

      // Re-send the request with the updated config
      return apiClient(config);
    }

    return Promise.reject(error);
  }
);

module.exports = apiClient;

Best Practices for Axios Retries