Fetch vs Axios: Which Is the Best Library for Making HTTP Requests?

Fetch vs Axios: Which Is the Best Library for Making HTTP Requests?

In our previous post “How to make HTTP requests with Axios,” we’ve explained how to use Axios for sending basic CRUD requests. One of the main benefits of using Axios is the automatic JSON stringification when sending requests. A feature not supported by the native Fetch API.

Our post concluded that Axios is a lightweight library that offers a lot of helpful functionality when dealing with HTTP requests. For example, Axios provides an easy way to track upload progress via its request config. Moreover, Axios allows you to define interceptors to automate specific tasks for requests easily.

However, let’s also take a look at the Fetch API. You can perform the same tasks with the Fetch API. On top of that, the Fetch API is already available in all modern browsers.

This article aims to provide you with an overview of both tools so you can make a better-informed decision when picking a tool to send HTTP requests.

We’ll compare:

  • CRUD operations
  • Automatic JSON parsing
  • Interceptors
  • Error handling
  • Request timeout functioning

Comparison: CRUD operations and automatic JSON parsing

Let’s first take a look at sending basic requests. Let’s retrieve some data from a publicly available API at

First, the Axios object exposes different methods to retrieve, send, update, or delete a resource from an API.

  • axios.get(url[, config])
  •[, data[, config]])
  • axios.put(url[, data[, config]])
  • axios.patch(url[, data[, config]])
  • axios.delete(url[, config])

For a GET request, the function accepts an API URL and an optional configuration object.

const axios = require('axios')
const getRequest = async () => {
    try {
        const response = await axios.get('');
    } catch (err) {

We can use the config object to send additional parameters or headers. You can find all possible request configuration parameters in the Axios documentation.

const response = await axios.get('', {
    params: {
        ID: 1
    headers: {'X-Custom-Header': 'foobar'},

Now, let’s take a look at the fetch() version. The fetch API is the same as the Axios API. Note that you can pass any method to the fetch() function via the options object.

  • fetch(url, [options])

As you can see, it accepts an API URL and an options object. We have to pass the method with the options object. Further, we can also pass headers, parameters, or a body with this object.

const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json;charset=UTF-8'
fetch('', options)
  .then(response => response.json())

Note how we have to manually parse the resolve response object to JSON before we can log the returned data. Axios automatically takes care of parsing the response to JSON.

Comparison: Interceptors

Axios provides interceptors for both requests and responses. In other words, you can modify a request before sending it to an API or modify a response object that’s been returned. It’s a powerful concept that allows the automation of specific tasks.

You can use request interceptors to automatically add authorization headers. Furthermore, response interceptors are useful for modifying the response object to fit a different format. It’s even possible to intercept error responses and send them to a monitoring tool.

Here’s an example of an Axios interceptor used in our previous tutorial.The axios.interceptors.request.use() method allows you to define a request interceptor.

axios.interceptors.request.use(config => { = 'my-axios-app'
    console.log(`Sending ${config.method} request to: ${config.url}`);
    return config;
}, error => {
    return Promise.reject(error);
// send GET request

How can we do this with fetch()? By default, fetch() doesn’t support a way to define request interceptors. Luckily, we can fall back to overwriting the function so we can add custom logic to the fetch method.

fetch = (originalFetch => {
    return (...arguments) => {
        console.log('Adding headers')
        arguments[1].headers = {
            'Accept': 'application/json'
      return originalFetch.apply(this, arguments);
fetch('', { method: 'GET' })
    .then(response => response.json())
    .then(data => {

As you can see, overwriting the original fetch() function affects the readability of your code. Personally, I prefer using Axios’ request and response interceptors. Yet, there’s an NPM package that allows you to register custom interceptors to the fetch() method in a more readable manner. The package is called fetch-intercept. Here’s an example that solves the problem.

const fetchIntercept = require('fetch-intercept')

const unregister = fetchIntercept.register({
    request: function (url, config) {
        // Modify the url or config here
        console.log(`Sending ${config.method} request to: ${url}`);
        return [url, config];
    response: function (response) {
        // Modify the reponse object
        console.log('Response received')
        return response;

// Call fetch to see your interceptors in action.
// Unregister your interceptor

Next, let’s learn how both tools take care of error handling.

Comparison: Error handling

It’s essential to understand the difference between Axios and fetch() in error handling. When Axios receives a 404 or 400 error, it will catch the error using the catch() block. This catch() block allows you to handle the error.

Instead, the Fetch API prefers completing the request successfully via the then() method but includes a 400 status. We can also find this in the fetch() documentation.

The Promise returned from fetch() won’t reject for a HTTP error status even if the response is an HTTP 404 or 500. Instead, it will resolve normally (with ok status set to false), and it will only reject on network failure or if anything prevented the request from completing.

You can try it out yourself using the below code.

const url = ''
    .then(response => console.log('good'))
    .catch(error => console.error('error')) // enters catch method -> prints 404
fetch(url, { method: 'GET' })
    .catch(error => console.log('BAD', error))
    .then(response => console.log('GOOD', response.status, response.statusText)); // enters then method -> prints 404 status

In the end, this is a personal preference. Some industry experts argue that the request didn’t fail, so it finished successfully. This is independent from whether the server could find the resource or not.

However, it’s a significant difference to know when working with one of both libraries.

Comparison: How to timeout a request?

Having the ability to define a timeout for a request prevents your application from hanging requests. These hanging requests can slow down your application or cause a memory leak, and therefore, are pretty dangerous.

As indicated by Roberto Vitillo, “Modern applications don’t crash; they hang. One of the main reasons for it is the assumption that the network is reliable. It isn’t.”

So, how do fetch() and Axios handle request timeouts?

First, Axios doesn’t set a default timeout. Therefore, it relies on the browser’s request timeout. In Chrome, a network request times out at 300 seconds. That’s way too long. Let’s change that using the config object. The example below aborts the request after a 1000 milliseconds waiting time.

const response = await axios.get('', {
    timeout: 1000

For fetch(), there was originally no way to set a timeout. Luckily, we can make use of the experimental Abort API that supports request timeouts. Here’s an example of how to abort a request after 1000ms with fetch().

const controller = new AbortController();
const fetchPromise = fetch('', { signal: controller.signal });  
// Abort request using setTimeout
setTimeout(() => controller.abort(), 1000); 
fetchPromise.then(response => {
    // Finished request

Note how we use the AbortController and pass the abort signal via our config object to the fetch() method. Once the setTimeout() timer runs out, it will call the abort() method on the controller object. This abort call will send the abort signal to our fetch() call.

Again, fetch() opts for a more lengthy workaround to implement a timeout for requests.


Axios provides a more comfortable to use API in comparison with fetch(). The most significant disadvantage of using fetch() is the verbose workaround to set a timeout for your request. It’s a much-needed functionality to prevent hanging requests.

On the other hand, remember that Axios sets the default timeout to zero. Therefore, don’t forget to set a timeout for each request. Alternatively, you can use a request interceptor to automatically set the request timeout.

Furthermore, for Node.js especifically, automatic JSON parsing is a useful feature to keep your code clean. Again, you can use an interceptor for the Fetch API to parse a response object automatically to JSON.

Yet, as seen in this guide, we have to overwrite the fetch() method to add custom logic. I think it adds complexity to your code. For that reason, Axios is a clear winner to send HTTP requests based on its simplicity and ease-of-use.

Open Source Session Replay

OpenReplay is an open-source, session replay suite that lets you see what users do on your web app, helping you troubleshoot issues faster. OpenReplay is self-hosted for full control over your data.


Start enjoying your debugging experience - start using OpenReplay for free.