How to Build and Use Custom Axios Adapters
Axios is a versatile HTTP client for JavaScript, primarily designed
to run in both Node.js (via http) and browser environments
(via XMLHttpRequest). By building custom adapters,
developers can replace this default transport layer with custom logic,
such as using the Fetch API, routing requests through Web Workers,
mocking responses for testing, or adding specialized caching mechanisms.
This article explains the structure of an Axios adapter, demonstrates
how to write a custom implementation, and covers how to register it
globally or per instance.
Understanding the Axios Adapter Interface
An adapter in Axios is a function that receives a configuration
object (config) and returns a Promise that resolves to a
standard Axios response object or rejects with an Axios error.
The returned response object must follow this structure:
{
data: {}, // The response body
status: 200, // HTTP status code
statusText: 'OK',// HTTP status message
headers: {}, // Response headers object
config: config, // The original request configuration
request: {} // The native request object (optional)
}If the request fails, the adapter should reject with an Error object
containing the same config, request,
response (if available), and an appropriate error
code.
Step 1: Writing a Custom Adapter
Below is an example of a custom adapter implemented using the modern
native fetch API:
// fetchAdapter.js
export function fetchAdapter(config) {
return new Promise((resolve, reject) => {
// 1. Build request URL and parameters
const url = new URL(config.url, config.baseURL);
if (config.params) {
Object.keys(config.params).forEach((key) =>
url.searchParams.append(key, config.params[key])
);
}
// 2. Prepare request options
const options = {
method: config.method ? config.method.toUpperCase() : 'GET',
headers: config.headers || {},
body: config.data,
signal: config.signal, // Support cancellation
};
// 3. Execute request
fetch(url.toString(), options)
.then(async (response) => {
// Parse data based on responseType (defaulting to JSON)
let responseData;
if (config.responseType === 'text') {
responseData = await response.text();
} else if (config.responseType === 'blob') {
responseData = await response.blob();
} else {
responseData = await response.json().catch(() => null);
}
// Convert Headers to a plain object
const responseHeaders = {};
response.headers.forEach((value, key) => {
responseHeaders[key] = value;
});
const axiosResponse = {
data: responseData,
status: response.status,
statusText: response.statusText,
headers: responseHeaders,
config: config,
request: null
};
// Resolve or reject based on validateStatus
const validateStatus = config.validateStatus || ((status) => status >= 200 && status < 300);
if (validateStatus(response.status)) {
resolve(axiosResponse);
} else {
const error = new Error(`Request failed with status code ${response.status}`);
error.config = config;
error.response = axiosResponse;
reject(error);
}
})
.catch((error) => {
// Handle network errors or aborted requests
error.config = config;
reject(error);
});
});
}Step 2: Plugging the Custom Adapter into Axios
You can apply custom adapters at three different levels depending on your use case.
1. Per-Instance (Recommended)
Attach the adapter to an Axios instance created using
axios.create():
import axios from 'axios';
import { fetchAdapter } from './fetchAdapter';
const apiClient = axios.create({
baseURL: 'https://api.example.com',
adapter: fetchAdapter,
});
apiClient.get('/users')
.then((response) => console.log(response.data))
.catch((error) => console.error(error));2. Globally
Override the default adapter for all standard axios
requests across the application:
import axios from 'axios';
import { fetchAdapter } from './fetchAdapter';
axios.defaults.adapter = fetchAdapter;
axios.get('https://api.example.com/data')
.then((response) => console.log(response.data));3. Per-Request
Specify an adapter for an individual call without modifying global or instance settings:
import axios from 'axios';
import { fetchAdapter } from './fetchAdapter';
axios.get('https://api.example.com/data', {
adapter: fetchAdapter
});Step 3: Supporting Adapter Chaining (Axios v1+)
Axios allows specifying an array of adapters. Axios iterates through the array and uses the first adapter that executes successfully or returns a valid response handler:
import axios from 'axios';
import { fetchAdapter } from './fetchAdapter';
const client = axios.create({
adapter: [fetchAdapter, 'xhr', 'http']
});In this setup, Axios attempts to use fetchAdapter. If it
is unavailable or throws a condition indicating it cannot handle the
request, Axios falls back to the built-in browser (xhr) or
Node.js (http) adapter.