sharmahritik2002@gmail.com

Mini axios from scratch - 23 Nov 2025

Building a toy version of axios.

I was curios about how axios internally works, so I tried building a mini version of it. Just enough to understand how it works under the hood.

While building, I learnt: interceptors, config merging, timeouts, aborting requests.

Let’s walk through it.


What We’re Trying to Recreate

Axios gives you a nice set of features:

That’s the entire goal — a toy model that captures these ideas, without the complexity.


Step 1: The MiniAxios Blueprint

Think of it as a little factory.

class MiniAxios {
  config = {
    timeout: 1000,
    headers: { "Content-Type": "application/json" },
  };

  requestInterceptors = [];
  responseInterceptors = [];

  constructor(config) {
    this.config = this.mergeConfig(config);
  }
}

Step 2: The Request Pipeline

Axios’s “magic” is just a long promise chain where every interceptor gets a chance to modify something.

async request({ url, config }) {
  const chain = [
    ...this.requestInterceptors,
    { successFn: this.dispatchRequest.bind(this, url) },
    ...this.responseInterceptors,
  ];

  let promise = Promise.resolve({ ...config });

  chain.forEach(({ successFn, errorFn }) => {
    promise = promise.then(
      (res) => successFn(res),
      (err) => errorFn && errorFn(err)
    );
  });

  return promise;
}

Step 3: The Network Layer (with Timeout + Abort)

When axios says “timeout”, it’s really just an AbortController waiting to cancel fetch.

async dispatchRequest(url, config) {
  const abortController = new AbortController();
  const finalConfig = this.mergeConfig(config);
  const timeout = finalConfig.timeout || 0;
  let timeoutId;

  if (timeout) {
    timeoutId = setTimeout(() => abortController.abort(), timeout);
  }

  try {
    return await fetch(`${finalConfig.baseUrl}${url}`, {
      ...finalConfig,
      signal: abortController.signal,
    });
  } finally {
    if (timeoutId) clearTimeout(timeoutId);
  }
}

A tiny timeout implementation. No heavy lifting.

Just: setTimeout → abort → fetch throws → promise rejects.

Step 4: Adding Interceptors

Axios lets you plug custom logic into the pipeline. we do the same — just store functions.

addRequestInterceptor(successFn, errorFn) {
  this.requestInterceptors.push({ successFn, errorFn });
}

addResponseInterceptor(successFn, errorFn) {
  this.responseInterceptors.push({ successFn, errorFn });
}

This lets you:

Format data

Everything fits into the same pipeline.

Step 5: A Simple get

async get(url, config = {}) {
  return this.request({
    url,
    config: { ...config, method: "GET" },
  });
}

Axios has more helpers, but the shape is identical.

Step 6: Creating an API Instance

const MyApi = MiniAxios.create({
  baseUrl: "https://jsonplaceholder.typicode.com",
  timeout: 1000,
});

Just like axios.create().

You configure once, use everywhere.

Step 7: Adding a Request Interceptor

MyApi.addRequestInterceptor((config) => {
  console.log("request interceptor", config);

  return {
    ...config,
    "x-api-key": "testing-req-interceptor",
  };
});

One place to attach your headers. Super useful for auth.

Using It in React

useEffect(() => {
  (async () => {
    const res = await MyApi.get("/todos");
    const data = await res.json();
    setTodos(data.slice(-5));
  })();
}, []);

Still the same fetch response under the hood, so .json() works exactly as expected.

The entire program flow:

Here’s a complete codesandbox link

Get in touch

Email me at sharmahritik2002@gmail.com sharmahritik2002@gmail.com link or follow me via my social links.