Fetch vs Axios: Which Is the Best Library for Making HTTP Requests?
Tags: react, reactnative, testing, webdev
Published: 2021-02-01 23:21:14

In our previous post [“How to make HTTP requests with Axios](https://blog.asayer.io/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](https://developer.mozilla.org/en-US/docs/Web/API/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 [typicode.com](https://jsonplaceholder.typicode.com/todos/).

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

- `axios.get(url[, config])`
- `axios.post(url[, 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.

```js
const axios = require('axios')
 
const getRequest = async () => {
    try {
        const response = await axios.get('https://jsonplaceholder.typicode.com/todos/1');
        console.log(response.data);
    } catch (err) {
        console.error(err);
    }
}
 
getRequest()
```

We can use the `config` object to send additional parameters or headers. You can find all possible [request configuration parameters](https://github.com/axios/axios#request-config) in the Axios documentation.

```js
const response = await axios.get('https://jsonplaceholder.typicode.com/todos', {
    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.

```js
const options = {
  method: 'GET',
  headers: {
    'Accept': 'application/json',
    'Content-Type': 'application/json;charset=UTF-8'
  }
}
 
fetch('https://jsonplaceholder.typicode.com/todos/1', options)
  .then(response => response.json())
  .then(console.log)
```

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](https://github.com/axios/axios#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.

```js
axios.interceptors.request.use(config => {
    config.name = 'my-axios-app'
    console.log(`Sending ${config.method} request to: ${config.url}`);
  
    return config;
}, error => {
    return Promise.reject(error);
});
  
// send GET request
axios.get('https://jsonplaceholder.typicode.com/todos/1')
```

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.

```js
fetch = (originalFetch => {
    return (...arguments) => {
        console.log('Adding headers')
 
        arguments[1].headers = {
            'Accept': 'application/json'
        }
 
      return originalFetch.apply(this, arguments);
    };
})(fetch);
  
fetch('https://jsonplaceholder.typicode.com/todos/1', { method: 'GET' })
    .then(response => response.json())
    .then(data => {
      console.log(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](https://www.npmjs.com/package/fetch-intercept). Here's an example that solves the problem.

```js
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.
fetch('https://jsonplaceholder.typicode.com/todos/1');
 
// Unregister your interceptor
unregister();
```

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](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API#differences_from_jquery).

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

```js
const url = 'https://jsonplaceholder.typicode.com/toodoos/1'
axios.get(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](https://robertovitillo.com/default-timeouts/), “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.

```js
const response = await axios.get('https://jsonplaceholder.typicode.com/todos/1', {
    timeout: 1000
});
```

For `fetch()`, there was originally [no way to set a timeout](https://github.com/whatwg/fetch/issues/951). Luckily, we can make use of the experimental [Abort API](https://developer.mozilla.org/en-US/docs/Web/API/AbortController#examples) that supports request timeouts. Here’s an example of how to abort a request after 1000ms with `fetch()`.

```js
const controller = new AbortController();
const fetchPromise = fetch('https://jsonplaceholder.typicode.com/todos/1', { signal: controller.signal });  
 
// Abort request using setTimeout
setTimeout(() => controller.abort(), 1000); 
 
fetchPromise.then(response => {
    // Finished request
    console.log(response)
})
```
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.

## Conclusion

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.