axios
attachResilience(instance) runs every request of an axios instance through the same pipeline as the fetch integration: a total timeout, retries with backoff and jitter that honour Retry-After, and a circuit breaker per host. It works by wrapping the instance’s adapter, so your interceptors, transforms and call sites stay as they are.
npm i @ylcnfrht/resilience axios # axios >= 1 (optional peer dependency)import axios from 'axios';import { attachResilience } from '@ylcnfrht/resilience/axios';
const api = axios.create({ baseURL: 'https://api.example.com' });const resilience = attachResilience(api, { name: 'orders-api', attemptTimeout: '3s' });
const { data } = await api.get('/orders/42'); // retried on 5xx / network errorsawait api.post('/orders', order, { headers: { 'Idempotency-Key': crypto.randomUUID() } });await api.get('/report', { resilience: { timeout: '2m', retries: 0 } }); // per-request override
resilience.eject(); // e.g. on shutdown: restores the previous adapterYou can also attach it to the default export: attachResilience(axios).
What each request goes through
Section titled “What each request goes through”total timeout (30 s) → retry (3×, exponential from 200 ms, full jitter, Retry-After) → per host: circuit breaker → concurrency limiter (optional) → attempt timeout (10 s) → axios adapterThe defaults, options and per-request fields are the same as for fetch. See the fetch options table. Only the client-specific parts differ:
| Option | Type | Default | Description |
|---|---|---|---|
onBeforeAttempt |
(config, context) => config | void |
none | Called before every attempt with a copy of the request config; return a replacement (e.g. a refreshed Authorization header). |
getAdapter |
axios.getAdapter |
taken from the instance, or import('axios') |
Resolves adapter names (['xhr', 'http', 'fetch']). Pass it in bundled browser code. |
All shared options: name, totalTimeout, attemptTimeout, retry, retryOn, circuitBreaker, concurrency, pipelineKey, maxPipelines, retryUnsafeMethods, timeProvider.
Per-request overrides
Section titled “Per-request overrides”Importing @ylcnfrht/resilience/axios adds a typed resilience field to AxiosRequestConfig:
await api.get('/health', { resilience: { retries: 0, timeout: '1s' } });await api.put('/profile', body, { resilience: { attemptTimeout: '20s' } });await api.post('/orders', order, { resilience: { idempotent: true } }); // the server deduplicatesFields: retries (number | false), retryOn, timeout, attemptTimeout, idempotent. Same meaning as for fetch.
How errors are classified
Section titled “How errors are classified”axios rejects non-2xx responses with an AxiosError that carries error.response. The integration reads that response, so status-based rules work the same as with fetch:
| Failure | Retried by default | Counted by the breaker |
|---|---|---|
Response 408 429 500 502 503 504 |
yes | yes |
| Other 4xx / 5xx response | no | no |
Error without a response, code ECONNRESET, ECONNREFUSED, ECONNABORTED, ETIMEDOUT, EPIPE, EAI_AGAIN, ENETUNREACH, EHOSTUNREACH, ERR_NETWORK, UND_ERR_SOCKET |
yes | yes |
Attempt timeout (TimeoutRejectedError) |
yes | yes |
Any other error without a response (ERR_CANCELED, a bad config…) |
no | no |
After the last retry, the caller gets the original AxiosError, so error.response.status and axios.isAxiosError(error) keep working. Rejections from the pipeline itself (BrokenCircuitError, TimeoutRejectedError, BulkheadRejectedError) come through unchanged; check them with isBrokenCircuitError() and friends from @ylcnfrht/resilience.
Only idempotent requests are retried: GET HEAD OPTIONS TRACE PUT DELETE, or any method with an Idempotency-Key header or resilience: { idempotent: true }. The rules are explained on the fetch page.
A response discarded for a retry with responseType: 'stream' has its stream destroyed, so the socket is released.
Token refresh per attempt
Section titled “Token refresh per attempt”Request interceptors run once per request, before the adapter. Retries happen inside the adapter, so they reuse the headers from the first attempt. To change something on every attempt, use onBeforeAttempt:
import { AxiosHeaders } from 'axios';
attachResilience(api, { name: 'crm', retryOn: [401, 429, 503], onBeforeAttempt: async (config) => ({ ...config, headers: new AxiosHeaders(config.headers as AxiosHeaders).set('Authorization', `Bearer ${await tokens.get()}`), }),});Each attempt receives the attempt’s own signal, so the attempt timeout and the caller’s signal abort the underlying request.
Bundled browser code
Section titled “Bundled browser code”axios’ default adapter is a list of names (['xhr', 'http', 'fetch']) that only axios can resolve. The integration gets getAdapter from the default export, or loads axios with a dynamic import() on first use. Bundlers may not resolve that import; pass it explicitly:
import axios from 'axios';
attachResilience(api, { getAdapter: axios.getAdapter });If the instance already has a function as its adapter (a mock adapter, a custom one), that function is wrapped directly and getAdapter isn’t used.
Related
Section titled “Related”- fetch: full option reference and idempotency rules.
- Migrating from axios-retry
- Retry, Circuit breaker