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
| Item | Why |
|---|---|
Set skipWhenAuthPresent: true on CachingPlugin | Prevents caching user-specific responses on a shared instance |
Keep storeAuthRequests: false on ManualRetryPlugin | Prevents storing requests with auth headers that may have expired |
Set debug: false in production | Avoids logging request/response details to stdout/stderr |
Use maxRequestsToStore | Bounds memory usage from ManualRetryPlugin's request store |
| Prefer one retryer per security boundary | Isolates cache and store from other users/tenants |
Add Idempotency-Key to POST/PUT mutations | Makes retried mutations safe to replay |
Call destroy() on shutdown | Clears 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();
});