Frontend Development 18 min read

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.

ByteFE
ByteFE
ByteFE
Deep Dive into Axios: Core Architecture, Request Flow, and Customization

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.js

Overview

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

Front-endJavaScriptsource-code-analysisAxiosHTTPadapterrequest interceptor
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.