Client-Side Rate Limiting in Axios

Client-side rate limiting prevents applications from overwhelming APIs, avoiding 429 Too Many Requests errors and staying within usage quotas. While the Axios HTTP client does not have native, out-of-the-box rate limiting functionality, it provides powerful primitives—primarily interceptors and custom adapters—that allow developers to implement robust rate limiting, request throttling, token bucket algorithms, and automatic retry strategies seamlessly.

Axios Interceptors and Request Queuing

Axios interceptors are the primary mechanism for intercepting and altering requests before they are sent, or responses before they are processed. To implement rate limiting directly within Axios, you can use a request interceptor combined with a queue.

When a request is initiated, the interceptor can push the request execution into a delay mechanism or queue rather than executing it immediately:

import axios from 'axios';

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

let lastRequestTime = 0;
const MIN_INTERVAL_MS = 200; // Limit to 5 requests per second

apiClient.interceptors.request.use(async (config) => {
  const now = Date.now();
  const timeSinceLast = now - lastRequestTime;

  if (timeSinceLast < MIN_INTERVAL_MS) {
    const delay = MIN_INTERVAL_MS - timeSinceLast;
    await new Promise((resolve) => setTimeout(resolve, delay));
  }

  lastRequestTime = Date.now();
  return config;
});

Third-Party Throttling Libraries

For complex rate limiting—such as managing concurrent request limits, burst allowances, and reservoir refreshes—integrating specialized concurrency managers like bottleneck or p-throttle with Axios is standard practice.

Using Bottleneck

Bottleneck acts as a task scheduler that wraps Axios calls to ensure adherence to strict rate limits:

import axios from 'axios';
import Bottleneck from 'bottleneck';

const limiter = new Bottleneck({
  minTime: 250, // 4 requests per second
  maxConcurrent: 2 // Maximum 2 requests running simultaneously
});

const apiClient = axios.create();

// Wrap the Axios request call
export const throttledGet = limiter.wrap((url, config) => apiClient.get(url, config));

Using Axios Rate Limit Plugins

Packages such as axios-rate-limit wrap an existing Axios instance directly, applying a token bucket or sliding window algorithm behind the scenes without modifying how API calls are made across the codebase:

import axios from 'axios';
import rateLimit from 'axios-rate-limit';

// Creates an Axios instance that allows a maximum of 10 requests per 1000ms
const http = rateLimit(axios.create(), { 
  maxRequests: 10, 
  perMilliseconds: 1000,
  maxRPS: 10 
});

Handling 429 Responses and Exponential Backoff

Client-side rate limiting should also include reactive strategies when the server signals that limits have been breached. Using axios-retry or custom response interceptors allows Axios to respect server-provided Retry-After headers and apply exponential backoff:

import axios from 'axios';
import axiosRetry from 'axios-retry';

const apiClient = axios.create();

axiosRetry(apiClient, {
  retries: 3,
  retryCondition: (error) => error.response?.status === 429,
  retryDelay: (retryCount, error) => {
    const retryAfter = error.response?.headers['retry-after'];
    if (retryAfter) {
      return parseInt(retryAfter, 10) * 1000;
    }
    return axiosRetry.exponentialDelay(retryCount);
  }
});

Summary of Strategies

  1. Proactive Throttling: Intercept requests to space them out evenly before transmission.
  2. Concurrency Control: Limit the number of in-flight requests using queues.
  3. Reactive Backoff: Read Retry-After headers on 429 responses to pause and reschedule dropped requests.