Axios vs Fetch (2025): What's the Best Option for Making HTTP Requests? - Featured Image
Web development12 min read

Axios vs Fetch (2025): What's the Best Option for Making HTTP Requests?

When it comes to making HTTP requests, developers often ask a common question: which is better, Axios or the Fetch API? Both Axios and the Fetch API help developers handle HTTP/GET/POST requests in JavaScript. Understanding these technologies' strengths, differences, and use cases is crucial for modern web development.

Understanding the basic syntax of Axios and fetch()

Before we dive into more advanced features of Axios, let's compare its basic syntax to fetch(). Here's how you can use Axios to send a POST request with custom headers to a URL. Axios automatically converts the data to JSON, so you don't have to:

// axios
const url = 'https://jsonplaceholder.typicode.com/posts'
const data = {
  a: 10,
  b: 20,
};
axios
  .post(url, data, {
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json;charset=UTF-8",
    },
  })
  .then(({data}) => {
    console.log(data);
});

Now compare this code to the fetch() version, which produces the same result:

// fetch()
const url = "https://jsonplaceholder.typicode.com/todos";
const options = {
  method: "POST",
  headers: {
    Accept: "application/json",
    "Content-Type": "application/json;charset=UTF-8",
  },
  body: JSON.stringify({
    a: 10,
    b: 20,
  }),
};
fetch(url, options)
  .then((response) => response.json())
  .then((data) => {
    console.log(data);
  });

Notice that to send data, fetch() uses the body property for a post request to send data to the endpoint, while Axios uses the data property. The data in fetch() is transformed into a string using the JSON.stringify method. Axios automatically transforms the data returned from the server, but with fetch() you have to call the response.json method to parse the data to a JavaScript object.

Backward compatibility

One of the main selling points of Axios is its wide browser support. Even old browsers like IE11 can run Axios without any issues. This is because it uses XMLHttpRequest under the hood. The Fetch API, on the other hand, only supports Chrome 42+, Firefox 39+, Edge 14+, and Safari 10.1+ (you can see the full compatibility table on CanIUse.com).

If your only reason for using Axios is backward compatibility, you don't need an HTTP library. Instead, you can use fetch() with a polyfill to implement similar functionality on web browsers that don't support fetch().

To use the fetch() polyfill, install it via the npm command like so:

npm install whatwg-fetch --save

Then, you can make requests like this:

import 'whatwg-fetch'
window.fetch(...)

Keep in mind that you might also need a promise polyfill in some old browsers.

Automatic JSON data transformation

As we saw earlier, Axios automatically stringifies the data when sending requests (though you can override the default behavior and define a different transformation mechanism). When using fetch(), however, you'd have to do it manually.

Compare the two below:

// axios
axios.get('https://api.github.com/orgs/axios')
  .then(response => {
    console.log(response.data);
  }, error => {
    console.log(error);
  });

// fetch()
fetch('https://api.github.com/orgs/axios')
  .then(response => response.json())    // one extra step
  .then(data => {
    console.log(data) 
  })
  .catch(error => console.error(error));

Automatic data transformation is a nice feature, but again, it's not something you can't do with fetch().

HTTP interceptors

One of Axios's key features is its ability to intercept HTTP requests. HTTP interceptors come in handy when you need to examine or change HTTP requests from your application to the server or vice versa (e.g., logging, authentication, or retrying a failed HTTP request).

With interceptors, you won't have to write separate code for each HTTP request. HTTP interceptors are helpful when you want to set a global strategy for how you handle requests and responses.

Here's how you can declare a request interceptor in Axios:

axios.interceptors.request.use((config) => {
  // log a message before any HTTP request is sent
  console.log("Request was sent");
  return config;
});

// sent a GET request
axios.get("https://api.github.com/users/sideshowbarker").then((response) => {
  console.log(response.data);
});

In this code, the axios.interceptors.request.use() method defines code to be run before an HTTP request is sent. Also, axios.interceptors.response.use() can be used to intercept the response from the server.

By default, fetch() doesn't provide a way to intercept requests, but it's not hard to come up with a workaround. You can overwrite the global fetch() method and define your interceptor, like this:

fetch = (originalFetch => {
  return (...arguments) => {
    const result = originalFetch.apply(this, arguments);
      return result.then(console.log('Request was sent'));
  };
})(fetch);

fetch('https://api.github.com/orgs/axios')
  .then(response => response.json())
  .then(data => {
    console.log(data) 
  });

Download progress with Axios vs. fetch()

Progress indicators are very useful when loading large assets, especially for users with slow internet. Previously, JavaScript programmers used the XMLHttpRequest.onprogress callback handler to implement progress indicators.

Implementing a progress indicator in Axios is simple. You can use the onDownloadProgress event that tracks the progress of your download:

function downloadFile() {
  getRequest(
    "https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg"
  );
}

function getRequest(url) {
  axios
    .get(url, {
      responseType: "blob",
      onDownloadProgress: (progressEvent) => {
        const percent = Math.round(
          (progressEvent.loaded * 100) / progressEvent.total
        );
        console.log(`Downloaded ${percent}%`);
      },
    })
    .then(function (response) {
      document
        .getElementById("img")
        .setAttribute("src", URL.createObjectURL(response.data));
    })
    .catch(function (error) {
      console.log(error);
    });
}

On the other hand, the Fetch API doesn't have an onprogress nor an onDownloadProgress event. Instead, it provides an instance of ReadableStream via the body property of the response object.

The following example illustrates the use of ReadableStream to provide users with immediate feedback during image download:

function downloadFile() {
  getRequest(
    "https://fetch-progress.anthum.com/30kbps/images/sunrise-baseline.jpg"
  );
}

function getRequest(url) {
  fetch(url)
    .then((response) => {
      if (!response.ok) {
        throw Error(response.status + " " + response.statusText);
      }
      // ensure ReadableStream is supported
      if (!response.body) {
        throw Error("ReadableStream not yet supported in this browser.");
      }
      // store the size of the entity-body, in bytes
      const contentLength = response.headers.get("content-length");
      // ensure contentLength is available
      if (!contentLength) {
        throw Error("Content-Length response header unavailable");
      }
      // parse the integer into a base-10 number
      const total = parseInt(contentLength, 10);
      let loaded = 0;
      return new Response(
        // create and return a readable stream
        new ReadableStream({
          start(controller) {
            const reader = response.body.getReader();
            read();
            function read() {
              reader
                .read()
                .then(({ done, value }) => {
                  if (done) {
                    controller.close();
                    return;
                  }
                  loaded += value.byteLength;
                  const progress = loaded / total;
                  console.log(`Downloaded ${Math.round(progress * 100)}%`);
                  controller.enqueue(value);
                  read();
                })
                .catch((error) => {
                  console.error(error);
                  controller.error(error);
                });
            }
          },
        })
      );
    })
    .then((response) => {
      // construct a blob from the data
      return response.blob();
    })
    .then((data) => {
      // insert the downloaded image into the page
      document
        .getElementById("img")
        .setAttribute("src", URL.createObjectURL(data));
    })
    .catch((error) => {
      console.error(error);
    });
}

Making simultaneous requests

To make multiple, simultaneous requests, Axios provides the axios.all() method. Simply pass an array of requests to this method, then use axios.spread() to assign the properties of the response array to separate variables:

axios.all([
  axios.get('https://api.github.com/users/iliakan'), 
  axios.get('https://api.github.com/users/taylorotwell')
])
.then(axios.spread((obj1, obj2) => {
  // Both requests are now complete
  console.log(obj1.data.login + ' has ' + obj1.data.public_repos + ' public repos on GitHub');
  console.log(obj2.data.login + ' has ' + obj2.data.public_repos + ' public repos on GitHub');
}));

You can achieve the same result by using the built-in Promise.all() method. Pass all fetch() requests as an array to Promise.all(). Next, handle the response by using an async function, like this:

Promise.all([
  fetch('https://api.github.com/users/iliakan'),
  fetch('https://api.github.com/users/taylorotwell')
])
.then(async([res1, res2]) => {
  const a = await res1.json();
  const b = await res2.json();
  console.log(a.login + ' has ' + a.public_repos + ' public repos on GitHub');
  console.log(b.login + ' has ' + b.public_repos + ' public repos on GitHub');
})
.catch(error => {
  console.log(error);
});

Effectively handling responses

Response management is a critical part of every application invoking an API. In this section, we will briefly look at the two aspects of it: getting the error code and manipulating response data.

Error management is different in Axios and the Fetch API. Specifically, fetch() doesn't automatically reject the promise in the event of server-side errors, such as HTTP 404 or 500 status codes. This means that these errors don't trigger the .catch() block, unlike in Axios, where such responses would typically be considered exceptions.

Instead, fetch() will resolve the promise normally with the ok status in the response set to false. The call to fetch() will only fail on network failures or if anything has prevented the request from completing.

In the following code, you can see how to handle errors in fetch():

try {
  const res = await fetch('...');

  if (!res.ok) {
    // Error on the response (5xx, 4xx)
    switch (res.status) {
      case 400: /* Handle */ break;
      case 401: /* Handle */ break;
      case 404: /* Handle */ break;
      case 500: /* Handle */ break;
    }
  }

  // Here the response can be properly handled
} catch (err) {
    // Error on the request (Network error)
}

Meanwhile, in Axios, you can discriminate all errors in a proper catch block as shown in the following example:

try {
  let res = await axios.get('...');
  // Here the response can be properly handled
} catch (err) {
  if (err.response) {
    // Error on the response (5xx, 4xx)
  } else if (err.request) {
    // Error on the request (Network error)
  }
}

Once the request has been served with a proper response without any errors, you can handle the response payload that will be accessible by using two different mechanisms.

In fetch(), the request/response payload is accessible in the body field and must be stringified, while in Axios it is in the data field as a proper JavaScript object. This difference is captured in the following, stripped-down examples:

// Using Fetch API
fetch('...')
  .then(response => response.json())
  .then(data => console.log(data))
  .catch(error => console.error('Error:', error)); 

// Using Axios
axios.get('...')
  .then(response => console.log(response.data))
  .catch(error => console.error('Error:', error));

The key difference in fetch() lies in the use of the .json() method. Despite the name, this method does not produce JSON. Instead, it will take JSON as an input and parse it to produce a JavaScript object.

Advanced use cases of Axios vs Fetch

In this section, we will look into some advanced use cases of Axios and fetch(), like handling response timeouts, cancelling requests, and streaming requests. You'll often need these features in real-world applications.

Handling response timeouts

The simplicity of setting a timeout in Axios is one of the reasons some developers prefer it to fetch(). In Axios, you can use the optional timeout property in the config object to set the number of milliseconds before the request is aborted.

Here's an example:

axios
  .get(
    "https://overpass-api.de/api/interpreter?data=[out:json];way[highway](40.5,-74,41,-73.5);out qt 30000;",
    { timeout: 4000 }
  )
  .then((response) => {
    console.log(response);
  })
  .catch((error) => console.error("timeout exceeded"));

fetch() provides similar functionality through the AbortController interface. However, it's not as simple as the Axios version:

const controller = new AbortController();
const options = {
  method: "GET",
  signal: controller.signal,
};

const promise = fetch(
  "https://overpass-api.de/api/interpreter?data=[out:json];way[highway](40.5,-74,41,-73.5);out qt 30000;",
  options
);
const timeoutId = setTimeout(() => controller.abort(), 4000);

promise
  .then((response) => {
    console.log(response);
  })
  .catch((error) => console.error("timeout exceeded"));

Here, we created an AbortController object which allows us to abort the request later using the abort() method. Signal is a read-only property of AbortController, providing a means to communicate with a request or abort it.

Cancelling requests

As we've just seen, we can make use of the abort() method of AbortController to cancel requests made with the Fetch API:

const controller = new AbortController();

const getRequest = () => {
  const options = {
    method: "GET",
    signal: controller.signal,
  };

  fetch(
    "https://overpass-api.de/api/interpreter?data=[out:json];way[highway](40.5,-74,41,-73.5);out qt 10000;",
    options
  )
    .then((response) => {
      console.log(response);
    })
    .catch((error) => console.error(error));
};

const cancelRequest = () => {
  controller.abort();
};

In Axios, an optional cancelToken property in the config object is provided to allow cancelling requests:

const source = axios.CancelToken.source();

const getRequest = () => {
  axios
    .get(
      "https://overpass-api.de/api/interpreter?data=[out:json];way[highway](40.5,-74,41,-73.5);out qt 10000;",
      {
        cancelToken: source.token,
      }
    )
    .then((response) => {
      console.log(response);
    })
    .catch((error) => console.error(error));
};

const cancelRequest = () => {
  source.cancel();
};

Streaming

We've been introduced to streaming when we looked at download progress. But Axios by default does not support streaming in the browser. The onDownloadProgress is the closest thing to streaming in the browser, but outside the browser, we can set a responseType of stream to use.

For fetch(), we can use the ReadableStream from the response body, as we saw earlier in the Download Progress section.

Axios vs. Fetch: What do developers say?

Developers are big believers in, "if it isn't broken, don't fix it". Devs who have been in big companies with thousands of lines of code wouldn't want to change something that works fine.

As reflected in a Reddit discussion on Axios vs Fetch, this is how most developers feel about switching from Axios to Fetch:

  • Error handling: Many developers prefer Axios to Fetch because throwing errors on 4xx and 5xx status codes comes by default. On the other hand, Fetch requires a manual check, as we've seen earlier.

  • Ease of use: The built-in functionalities of Axios, like auto-parsing JSON, interceptors, timeouts, etc, make it a constant go-to for many developers. As a developer highlighted: "I use it (Axios) for the interceptors feature, which afaik fetch doesn't have. Sure I could build my own but then I'd have to build my own".

  • Backward compatibility: Axios is backward-compatible with IE11 (which some devices still use today). But that doesn't rule out Fetch as polyfills make it backward-compatible.

  • Performance considerations: Fetch has a smaller bundle size, even with a polyfill, and does not require additional abstractions that reduce processing time.

  • Flexibility: Axios supports sending body with a GET request, whereas Fetch is stuck with the adherence to HTTP standards, even though there are a few developers demanding this feature since 2015.

How to configure CORS

Cross-Origin Resource Sharing (CORS) is an HTTP feature that allows a server to let resources be accessed from different origins. This is useful when you want to fetch data from public or authorized external APIs. Without CORS, browsers block these requests for security reasons.

If CORS is not set up correctly on the server, any request from another origin will fail with a "No Access-Control-Header-Present" error. To fix this, you need to configure the server to allow cross-origin requests. This includes adding the Access-Control-Allow-Origin header in the server's response. Once this is set up properly, the server will handle cross-origin requests automatically.

One common mistake is trying to set the Access-Control-Allow-Origin header in the request using Axios or fetch(). This is incorrect because that header must come from the server's response. Also, when custom headers are added to requests, the browser sends a preflight OPTIONS request first to check if the real request is allowed. This is part of how CORS works to keep web interactions safe.

Conclusion

Axios provides an easy-to-use API in a compact package for most HTTP communication needs. However, if you prefer to stick with native APIs, nothing is stopping you from implementing Axios features. As discussed in this article, it's possible to reproduce the key features of the Axios library using the fetch() method provided by web browsers. Whether it's worth loading a client HTTP API depends on whether you're comfortable working with built-in APIs.

hassaankhan789@gmail.com

Frontend Web Developer

Posted by





Subscribe to our newsletter

Join 2,000+ subscribers

Stay in the loop with everything you need to know.

We care about your data in our privacy policy

Background shadow leftBackground shadow right

Have something to share?

Write on the platform and dummy copy content

Be Part of Something Big

Shifters, a developer-first community platform, is launching soon with all the features. Don't miss out on day one access. Join the waitlist: