Deep Dive into Axios: Core Architecture, Request Flow, and Customization
This article provides a comprehensive analysis of Axios, covering its core directory structure, internal request/response flow, interceptor mechanism, data transformation, adapter handling, and ways to customize or replace default behavior, illustrated with extensive source code excerpts.
Introduction
When it comes to JavaScript HTTP requests, Axios stands out as the dominant front‑end network request library, widely used in many web projects.
Popularity of several popular HTTP request libraries on GitHub
Popular JS HTTP Request Library
Feature Summary
Stars
Forks
Axios
Promise‑based, works in browsers and Node
85.4k
8.3k
Request
Non‑Promise, simplified HTTP
25.2k
3.1k
Fetch
Promise‑based, no Node support
24.8k
3k
Superagent
—
15.7k
1.3k
Although all of them wrap XMLHttpRequest , Axios’s popularity is far ahead, indicating its excellence as an open‑source project. Yet many developers use it without reading its source code. Below is the core directory structure of the Axios project:
lib
└─ adapters
├─ http.js // Node environment uses the http module
├─ xhr.js // Browser environment uses xhr
└─ cancel
├─ Cancel.js
├─ CancelToken.js
├─ isCancel.js
└─ core
├─ Axios.js // creates Axios instance
├─ InterceptorManager.js // interceptors
├─ dispatchRequest.js // dispatches request
...
└─ helpers
├─ mergeConfig.js // merges config
├─ ...
├─ axios.js // entry file
├─ defaults.js // default config
├─ utils.jsOverview
Axios is a Promise‑based HTTP client that works in both Node.js and browsers. In Node it uses the native http module, while in the browser it uses XMLHttpRequest . Its key features include:
Creating XMLHttpRequests in the browser
Creating http requests in Node.js
Promise API support
Request and response interceptors
Request/response data transformation
Request cancellation
Automatic JSON conversion
Client‑side XSRF protection
Axios Internal Operation Flow
The following modules are involved in the flow:
Axios constructor
Request/response interceptors
dispatchRequest
Data transformation
Adapter handling
How Axios Supports Different Usage Patterns
Using Axios to Send Requests
Typical usage patterns:
// Method 1: axios(config)
axios({
method: 'get',
url: 'xxx',
data: {}
});
// Method 2: axios(url[, config]) – defaults to GET
axios('http://xxx');
// Method 3: shortcut methods
axios.request(config);
axios.get(url[, config]);
axios.post(url[, data[, config]]);
axios.put(url[, data[, config]]);
// ...
// Method 4: create an instance with custom config
const instance = axios.create({
baseURL: 'https://some-domain.com/api/',
timeout: 1000,
headers: {'X-Custom-Header': 'foobar'}
});
instance.get(url);
instance.post(url, data);
// ...Source Code Analysis
The entry file lib/axios.js creates an Axios instance:
// lib/axios.js
function createInstance(defaultConfig) {
// create Axios instance
var context = new Axios(defaultConfig);
// bind instance to Axios.prototype.request
var instance = bind(Axios.prototype.request, context);
// extend instance with prototype methods
utils.extend(instance, Axios.prototype, context);
// extend instance with context methods
utils.extend(instance, context);
// export the instance
return instance;
}
var axios = createInstance(defaults);
axios.create = function create(instanceConfig) {
return createInstance(mergeConfig(axios.defaults, instanceConfig));
};
module.exports = axios;
module.exports.default = axios;Calling axios() actually executes the function returned by createInstance , which points to Axios.prototype.request . The create method allows custom configuration while still invoking the core request method.
Request/Response Interceptors
Interceptors are essential for adding authentication tokens to requests or handling unauthorized responses globally. Example setup:
// Add request interceptor
axios.interceptors.request.use(function (config) {
config.headers.token = 'xxx';
return config;
});
// Add response interceptor
axios.interceptors.response.use(function (response) {
if (response.code === 401) {
login();
}
return response;
});Each Axios instance has an interceptors property containing request and response managers:
// lib/core/Axios.js
function Axios(instanceConfig) {
this.defaults = instanceConfig;
this.interceptors = {
request: new InterceptorManager(),
response: new InterceptorManager()
};
}
// lib/core/InterceptorManager.js
function InterceptorManager() {
this.handlers = [];
}
InterceptorManager.prototype.use = function (fulfilled, rejected, options) {
this.handlers.push({ fulfilled, rejected, ...options });
return this.handlers.length - 1; // return index for ejection
};The request flow is: request interceptor → http request → response interceptor . Internally, Axios builds two chains (request and response) and executes them via a Promise chain:
// lib/core/Axios.js (excerpt)
var requestInterceptorChain = [];
this.interceptors.request.forEach(function (interceptor) {
requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
});
var responseInterceptorChain = [];
this.interceptors.response.forEach(function (interceptor) {
responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
});
var chain = [dispatchRequest, undefined];
Array.prototype.unshift.apply(chain, requestInterceptorChain);
chain.concat(responseInterceptorChain);
var promise = Promise.resolve(config);
while (chain.length) {
promise = promise.then(chain.shift(), chain.shift());
}
return promise;dispatchRequest
Source Code Analysis
After interceptors, the request reaches dispatchRequest , which transforms request data, selects an adapter, and processes the response:
// lib/core/dispatchRequest.js
module.exports = function dispatchRequest(config) {
// transform request data
config.data = transformData.call(
config,
config.data,
config.headers,
config.transformRequest
);
// select adapter (default or custom)
var adapter = config.adapter || defaults.adapter;
// execute adapter and handle response
return adapter(config).then(function onAdapterResolution(response) {
// transform response data
response.data = transformData.call(
config,
response.data,
response.headers,
config.transformResponse
);
return response;
}, function onAdapterRejection(reason) {
return Promise.reject(reason);
});
};Transform Request / Response Data
Source Code Analysis
// lib/core/transformData.js
module.exports = function transformData(data, headers, fns) {
utils.forEach(fns, function transform(fn) {
data = fn(data, headers);
});
return data;
};The default configuration defines transformRequest and transformResponse arrays. For example, transformRequest serializes objects to JSON and sets appropriate Content-Type headers:
// lib/default.js (excerpt)
transformRequest: [function transformRequest(data, headers) {
if (utils.isFormData(data) || utils.isArrayBuffer(data) || utils.isBuffer(data) ||
utils.isStream(data) || utils.isFile(data) || utils.isBlob(data)) {
return data;
}
if (utils.isArrayBufferView(data)) {
return data.buffer;
}
if (utils.isURLSearchParams(data)) {
setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8');
return data.toString();
}
if (utils.isObject(data) || (headers && headers['Content-Type'] === 'application/json')) {
setContentTypeIfUnset(headers, 'application/json');
return JSON.stringify(data);
}
return data;
}],
transformResponse: [function transformResponse(data) {
if (strictJSONParsing || (forcedJSONParsing && utils.isString(data) && data.length)) {
try {
return JSON.parse(data);
} catch (e) { /* ignore */ }
}
return data;
}],These transformations implement the documented feature of automatic JSON conversion.
Overriding / Extending Transform Methods
Since transformRequest is part of the default config, developers can replace or extend it:
import axios from 'axios';
// Override the whole array
axios.defaults.transformRequest = [(data, headers) => {
// custom logic
return data;
}];
// Append a new transformer
axios.defaults.transformRequest.push((data, headers) => {
// additional processing
return data;
});Adapter (Adapter) Handling
The second task of dispatchRequest is to determine the adapter . If no custom adapter is provided, Axios selects one based on the runtime environment:
// lib/default.js (excerpt)
function getDefaultAdapter() {
var adapter;
if (typeof XMLHttpRequest !== 'undefined') {
// Browser – use xhr adapter
adapter = require('./adapters/xhr');
} else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') {
// Node – use http adapter
adapter = require('./adapters/http');
}
return adapter;
}
var defaults = {
// ... other defaults
adapter: getDefaultAdapter(),
// ...
};This environment detection gives Axios its isomorphic capability.
Custom Adapter Example
By providing a custom adapter, developers can mock requests or implement alternative transport mechanisms:
const mockUrl = {
'/mock': { data: xxx }
};
const instance = axios.create({
adapter: (config) => {
if (!mockUrl[config.url]) {
// fall back to default adapter to avoid recursion
delete config.adapter;
return axios(config);
}
return new Promise((resolve, reject) => {
resolve({
data: mockUrl[config.url],
status: 200
});
});
}
});Conclusion
Due to length constraints, this article only covers part of Axios’s source code; modules such as client‑side XSRF protection and request cancellation are omitted. Readers are encouraged to explore the GitHub repository for a deeper understanding.
References:
Getting Started | Axios Docs – https://github.com/axios/axios/blob/master/lib/axios.js
ByteFE
Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.