Docs Guides

Production Setup

Recommended plugin combinations and configuration decisions for production deployments. Start from the right defaults and avoid the sharp edges.

Frontend single-user client

Typical SPA or mobile app calling one backend service:

import {
  createRetryer,
  RETRY_MODES,
  AXIOS_RETRYER_BACKOFF_TYPES,
  AXIOS_RETRYER_HTTP_METHODS,
} from 'axios-retryer';
import { createTokenRefreshPlugin } from 'axios-retryer/plugins/TokenRefreshPlugin';
import { createCircuitBreaker } from 'axios-retryer/plugins/CircuitBreakerPlugin';
import { createCachePlugin } from 'axios-retryer/plugins/CachingPlugin';
import { createMetricsPlugin } from 'axios-retryer/plugins/MetricsPlugin';
import { createDebugSanitizationPlugin } from 'axios-retryer/plugins/DebugSanitizationPlugin';
import axios from 'axios';

// Chain unconditional plugins on one expression so `on()` is typed for all of them
export const api = createRetryer({
  axiosInstance: axios.create({
    baseURL: import.meta.env.VITE_API_URL,
    timeout: 10_000,
  }),
  mode: RETRY_MODES.AUTOMATIC,
  retries: 3,
  backoffType: AXIOS_RETRYER_BACKOFF_TYPES.EXPONENTIAL,
  maxConcurrentRequests: 8,
  retryableStatuses: [408, 429, [500, 599] as const],
  retryableMethods: [
    AXIOS_RETRYER_HTTP_METHODS.GET,
    AXIOS_RETRYER_HTTP_METHODS.HEAD,
    AXIOS_RETRYER_HTTP_METHODS.OPTIONS,
  ],
  debug: import.meta.env.DEV,
})
  .use(
    createTokenRefreshPlugin(async (axiosInstance) => {
      const refresh = localStorage.getItem('refreshToken');
      if (!refresh) throw new Error('No refresh token');
      const { data } = await axiosInstance.post('/auth/refresh', { refresh });
      localStorage.setItem('accessToken', data.accessToken);
      return { token: data.accessToken };
    }, { maxRefreshAttempts: 2 }),
  )
  .use(
    createCircuitBreaker({
      failureThreshold: 5,
      openTimeout: 30_000,
      halfOpenMax: 2,
      successThreshold: 2,
    }),
  )
  .use(
    createCachePlugin({
      timeToRevalidate: 60_000,
      maxItems: 300,
      skipWhenAuthPresent: true,
    }),
  )
  .use(createMetricsPlugin());

api.on('onTokenRefreshed', () => console.info('Token refreshed'));

api.on('onMetricsUpdated', (m) => {
  if (m.completelyFailedRequests > 10) {
    analytics.track('high_api_failure_rate', { count: m.completelyFailedRequests });
  }
});

// Optional dev-only plugin (runtime registration; typings unchanged — plugin adds no events)
if (import.meta.env.DEV) {
  api.use(
    createDebugSanitizationPlugin({
      sanitizeOptions: {
        sensitiveHeaders: ['Authorization'],
        sensitiveFields: ['password', 'token'],
      },
    }),
  );
}

window.addEventListener('beforeunload', () => api.destroy());

Backend Node.js service

Multi-user Node.js service calling a downstream API. Do NOT share a caching instance across users:

import { createRetryer } from 'axios-retryer';
import { createCircuitBreaker } from 'axios-retryer/plugins/CircuitBreakerPlugin';
import { createMetricsPlugin } from 'axios-retryer/plugins/MetricsPlugin';

// One shared retryer per downstream service — NOT per request
export const downstreamApi = createRetryer({
  retries: 3,
  maxConcurrentRequests: 20,
  queueDelay: 0,
  maxQueueSize: 500,
})
  .use(
    createCircuitBreaker({
      failureThreshold: 10,
      openTimeout: 30_000,
      useSlidingWindow: true,
      slidingWindowSize: 60_000,
    }),
  )
  .use(createMetricsPlugin());

downstreamApi.on('onMetricsUpdated', (m) => {
  prometheus.gauge('downstream_api_failures').set(m.completelyFailedRequests);
});

process.on('SIGTERM', () => {
  downstreamApi.destroy();
});

Security checklist

ItemWhy
Set skipWhenAuthPresent: true on CachingPluginPrevents caching user-specific responses on a shared instance
Keep storeAuthRequests: false on ManualRetryPluginPrevents storing requests with auth headers that may have expired
Set debug: false in productionAvoids logging request/response details to stdout/stderr
Use maxRequestsToStoreBounds memory usage from ManualRetryPlugin's request store
Prefer one retryer per security boundaryIsolates cache and store from other users/tenants
Add Idempotency-Key to POST/PUT mutationsMakes retried mutations safe to replay
Call destroy() on shutdownClears timers and prevents memory leaks in long-running processes

Timer health monitoring

// Check timer health periodically in long-running services
const healthInterval = setInterval(() => {
  const stats = downstreamApi.getTimerStats();
  if (stats.activeTimers > 200) {
    logger.warn('Elevated timer count', stats);
  }
}, 30_000);

process.on('SIGTERM', () => {
  clearInterval(healthInterval);
  downstreamApi.destroy();
});
Offline Support →