Rust vs. C in networking application
Close

22 September 2022

Software Development

Axios vs. Fetch API – Which is Better For HTTP Requests?

34 minutes reading

Axios vs. Fetch API – Which is Better For HTTP Requests?

Whether making a network request for cat photos or network latency data, we need a tool that allows us to perform such operations. Retrieving or modifying API data from a server is a vital part of most web applications. Use cases include:

  • loading user information,
  • receiving updates from the server,

and many, many more. In this article, we'll compare two of the most widely-used options for making HTTP requests - Axios and Fetch.

Quick overview of Axios and Fetch API

If you've worked with JavaScript lately, there's a good chance you worked with Axios. As of the time of writing, its npm stats say that it's being downloaded 136M times per month!

In 2015, Fetch API was introduced. Developers finally could forget about ugly and hard to use HTMLHttpRequests. However, the popularity of Axios didn't suffer. On the contrary, check out the downloads graph since 2015.

Axios downloads graph

Fig. 1 Axios downloads graph

When Axios was introduced and became popular, there wasn't another tool that was as easy to use and advanced as Axios. Native XMLHttpRequest API was hard to use and didn't provide that many functionalities. However, why after 2015 are we still using Axios?

Axios

Axios is a third-party HTTP client library for making network requests. It is promise-based, which can be seen in the following example:

axios.get('https://codilime.com/')
  .then(response => console.log(response.data));

The response data is available as a JSON by default, hence we can immediately access it using the data property. It uses XMLHttpRequest under the hood, which is one of the reasons for its wide browser support.

You can add it to your project via a content distribution network (CDN) or by using a package manager (like npm).

Core features include:

We'll see all of those and more later in the article! Now, let's take a look at Fetch.

Fetch

Fetch, like Axios, is a promise-based HTTP client. Let's see a simple example:

fetch('https://codilime.com/')
  .then(response => response.json())
  .then(console.log);

We'll focus on the syntax later in the article. For now, we just need to know that Fetch API provides a simple-to-use interface for fetching resources. To be more specific, it provides us with ways for accessing and manipulating parts of the HTTP pipeline, part of which are requests and responses.

Fetch API provides the fetch() method, which we'll examine in this article.

It's important to know that with the Fetch API we can fully reproduce all of the core features of Axios. Actually, Fetch API is a native interface with even more possibilities than Axios. However, because it's low-level, it can often be a little harder to use.

Installation and backward compatibility

Both Axios and fetch() can be used within a browser, as well as a node.js environment; check out the details below.

Axios

As pointed out earlier, Axios can be installed by using a CDN or package manager.

When using Axios within a browser, we can use a CDN:

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

In a node.js environment, we can use a package manager:

  • Install using NPM - npm install axios
  • Install using Yarn - yarn add axios

Now that we've got Axios installed, we have to import it into our project:

import axios from "axios";

And that's it! Axios is widely supported on most browsers, even old ones like IE11.

Fetch

Fetch is a built-in API, hence we don't have to install or import anything. It's available in all modern browsers, you can check it on caniuse. Fetch is also available in node.js - you can read more about it here.

Keep in mind that even if your browser doesn't support Fetch, you can always use a polyfill. However, if you have to use this polyfill, you might also need a promise polyfill too. Hopefully, nowadays you won't need either of them!

Syntax

Let's compare the basic syntax between Axios and fetch() before we dive into the deeper features of both of them.

Making a request

Axios

Axios provides many different ways of calling it:

axios(url, {
  // configuration options
})

Instead of specifying the HTTP method in configuration options, we can use dot notation:

axios.post(url, {
  // configuration options
})

If we leave out configuration options and dot notation, it defaults to the GET method:

axios(url)

We can use many different config settings for the request, using the second argument:

axios(url, {
  method: 'post',
  timeout: 1000,
  headers: {
    "Content-Type": "application/json",
  },
  data: {
    property: "value",
  },
})

You can find the full list here.

Fetch

Fetch, similar to axios, receives two arguments. The first argument is the URL, and the second is configuration options:

fetch(url, {
  method: 'POST',
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    property: "value",
  }),
})

For a full list of configuration options, check out this link.

A wrap-up

As you can see the basic syntax is very similar, almost identical to each other. There are multiple small differences, though:

  • Different properties are used for a post request to send data to the endpoint - Axios uses the data property, whereas with fetch we use the body property.
  • We need to serialize data into a JSON string to send data. Axios automatically stringifies data when sending JavaScript objects to the API using the POST method.
  • When using fetch(), we have to use JSON.stringify to transform data into a string.

Handling the response

Let's say we make a network request to fetch some data, how do we approach and handle the response?

Axios

In Axios, the response data is available as a JSON by default. All we have to do is access the data property on the response object:

axios.get('https://codilime.com/')
  .then(response => console.log(response.data));

The response type can be changed using the configuration options, namely the responseType property. It indicates the type of data that the server will respond with, options include:

  • arraybuffer
  • document
  • json (default)
  • text
  • stream
  • blob

Fetch

With Fetch, there is one more step we have to take before accessing the response data:

fetch('https://codilime.com/')
  .then(response => response.json())
  .then(console.log);

When using fetch(), we receive the response as soon as all of the headers have arrived. At that point, we don't have the body of the HTTP response, which is yet to be loaded. That is why we receive another promise. In short, response.json() waits for the body to load.

JSON objects, which we use every day, are usually relatively small. However, imagine a situation where you have to load a really large image. This might take a while, and we might need information about this before the image is completely loaded.

To read more about response.json() check out this link.

A wrap-up

Fetch is a relatively low-level API, which allows for precise control of the loading process. It comes with the cost of having to handle two promises every time you use it.

  • Fetch requires us to take one more step when handling responses, as it returns a promise, at which point we don't have the JSON data format which we require, hence the need for the .json() method.
  • To access response data in Axios, we use the data property, whereas when using fetch(), final data can be named any variable.

Error handling

Remember that both Axios and Fetch are promise-based HTTP clients? As such, they both return a promise which can be either resolved or rejected. However, that's it when it comes to similarities - the terms under which a promise gets resolved or rejected, differ significantly.

Axios

Let's see a typical example of error handling in Axios, using .catch():

axios.get('https://codilime.com/')
  .then(response => console.log(response.data))
  .catch((err) => console.log(err.message));

Every response contains a status property, which is simply an HTTP response status code. Those codes indicate whether a request has been successfully completed. Examples include:

  • 200 OK - the request succeeded,
  • 403 Forbidden - the client does not have access rights to the content,
  • 418 I'm a teapot - the server refuses the attempt to brew coffee with a teapot (no, it's not a joke, check it out here).

You can read more about those HTTP response status codes here.

Now that we know what the HTTP response status codes are, we can understand how Axios error handling works. Axios will reject any promise whose status code falls outside the 200-299 range (successful responses).

We can examine the error object to get more information about the error:

axios.get('https://codilime.com/')
  .then(response => console.log(response.data))
  .catch((err) => {
    if (err.response) {
      // The request was made, but the server responded with a status code that falls out of the 2xx range
      const { status } = err.response;

      if (status === 401) {
        // The client must authenticate itself to get the requested response
      }
      else if (status === 502) {
        // The server got an invalid response
      }
      ...
    }
    else if (err.request) {
      // The request was made, but no response was received
    } 
    else {
      // Some other error
    }
  });

When we have the response property on the error object, it means that the request was made, and the server responded, however, the response status code was outside the 2xx range. The request property on the other hand indicates that the request was made, but the response wasn't received. If both of those are untrue, it indicates an issue with setting up a network request, which triggers an error.

Fetch

Fetch error handling differs significantly from Axios. The most important difference is that it doesn't reject a promise if we get an HTTP error - unsuccessful responses are still resolved. Because of that, HTTP errors are handled within .then blocks. A Fetch API promise will be rejected only in case of network failure.

fetch('https://codilime.com/')
  .then(response => {
    if (!response.ok) {
      throw Error(`HTTP error: ${response.status}`);
    }
    return response.json();
  })
  .then(console.log)
  .catch((err) => {
    console.log(err.message)
  });

Working with Fetch API requires us to check the response.ok property to control HTTP errors.

The two most important response properties are status and ok:

  • status - response status code (integer).
  • ok - shorthand for checking that the status is in the range 2xx (boolean).

In a successful scenario, those will have 200 and true values, respectively:

{
  ...
  status: 200,
  ok: true,
  ...
}

However, in an error situation, we'll get an HTTP Error status code and false instead. For example, if we're not authenticated to receive the requested response, we'll get:

{
  ...
  status: 401,
  ok: false,
  ...
}

A wrap-up

In Axios, responses outside of the 200-299 range (successful responses) will automatically be rejected. Using a .catch() block, we can get information about the error, such as whether a response was received, and if so, its status code.

In fetch() on the other hand, unsuccessful responses are still resolved. That's why we have to check the response's ok property and throw an error when it's set to false. That error is then handled in the .catch() block.

Response timeout

The functionality to abort a request after a set amount of time is an essential part of making HTTP requests. Without that functionality the request can hang and might result in slowing down our application.

Axios

Setting up a response timeout with Axios is extremely easy. All we have to do is add one line to our request's configuration object:

axios.get(url, {
  ...
  timeout: 2000,
  ...
})
  .then(response => console.log(response.data))
  .catch(err => console.log(err.message))


Timeout is an optional parameter that takes time in milliseconds. The above request will be aborted if it takes longer than two seconds, and log an error. The default value for the timeout property is 0, which means no timeout.

Fetch

Doing the same with Fetch API is not as easy. To achieve the same behavior, we can make use of an interface called AbortController and the fetch() method configuration options:

const controller = new AbortController();
const signal = controller.signal;

setTimeout(() => controlller.abort(), 2000);

fetch('https://codilime.com/', {
  signal
})
  .then(response => {
    if (!response.ok) {
      throw Error(`HTTP error: ${response.status}`);
    }
    return response.json();
  })
  .then(console.log)
  .catch((err) => {
    console.log(err.message)
  });

By creating an AbortController object, we gain access to the abort() method, and signal object. We then set the timeout for two seconds, after which the request will be aborted using the abort() method. Finally, we pass the controller's signal as an option to fetch(), using its configuration options. This associates the signal and controller with the fetch request. Consequently, fetch() can terminate, whenever abort() is called.

Note that if you call abort() after fetch() has been completed, it will simply ignore it.

A wrap-up

fetch() doesn't provide us with a timeout configuration option like Axios. Instead, we have to make use of the AbortController interface and the setTimeout function. Axios hides a lot of this boilerplate, which makes it a clear winner for most use cases. However, we already know that Fetch is a low-level API. Depending on your needs, you might still prefer Fetch over Axios when it comes to response timeouts and canceling requests.

Intercepting HTTP requests and responses

Let's imagine a scenario where we must save a log of every HTTP request that is sent from our application. Adding such code to every request is cumbersome, error-prone, and in short, infeasible. That's when interceptors come into play. They allow us to eliminate repeating the code for each request, and instead create and set global behavior for handling requests and responses.

In a nutshell, HTTP interceptors are used to apply custom logic between the client-side and server-side HTTP requests and responses. They can be used for different operations, for example:

  • modifying HTTP headers or body,
  • setting a customized token,
  • modifying the response,

and many, many more. Let's see how we can create an interceptor that logs data in both Axios and Fetch.

Axios

As usual, Axios comes with a feature for the simple creation of HTTP interceptors - both response and requests ones. First, let's see a request interceptor:

axios.interceptor.request.use(config => {
  // log data before HTTP request is sent
  console.log('HTTP request being sent...');
  return config;
})

The above code results in a logging message before any HTTP request is sent. Similarly, a response interceptor is defined to run some code before they are handled by then or catch:

axios.interceptor.response.use(
  function (response) {
    // This function will be triggered on any status code inside 2xx range
    return response;
  },
  function (error) {
    // This function will be triggered on any status code outside of 2xx range
    return Promise.reject(error);
  }
)

A response interceptor might come in handy when you, for example, want to set up a policy to always retry once HTTP request in case of failure.

Axios also allows you to remove an interceptor if you wish:

const requestInterceptor = axios.interceptor.request.use(config => {
  // log data before HTTP request is sent
  console.log('HTTP request being sent...');
  return config;
});
axios.interceptor.request.eject(requestInterceptor);

Fetch

When using Fetch API, we don't have a configuration option or a special built-in function with which we can implement interceptors. However, Vanilla JS (a fast, lightweight, cross-platform framework) is enough:

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

The above code is an implementation of an HTTP request interceptor. We can use it the same as we would use the plain old fetch() function:

fetch('https://codilime.com/')
  .then(response => response.json())
  .then(console.log)

A wrap-up

Axios provides us with out-of-the-box features for creating HTTP requests and response interceptors. Similarly to response timeouts, it hides most of the boilerplate and offers us a nice, easy-to-use interface.

On the other hand, we have Fetch API, which again because of its low level, doesn't provide us with such a method. HTTP interceptors based on fetch() are easy to write, and find on the internet, though.

Once again we have a choice of avoiding boilerplate versus more fine-grained control and customization options. The choice should be based on the use case and individual needs.

Download progress

Back in the days when XMLHttpRequests were still widely used, it was the .onprogress method which was used to implement progress indicators. Today, those indicators are still an essential part of loading assets, especially big ones, and especially for users with slower internet connections. However, we don't have to fiddle with this interface anymore.

Let's download an image!

Axios

A progress bar is usually implemented using Axios Progress Bar when using the Axios library.

It's available through the NPM package:

npm install --save axios-progress-bar

or via a CDN:

<script src="https://cdn.rawgit.com/rikmms/progress-bar-4-axios/0a3acf92/dist/index.js"></script>

After that, we need to import the CSS into the HTML, or through JavaScript with a module bundler, like webpack:

<link rel="stylesheet" type="text/css" href="https://cdn.rawgit.com/rikmms/progress-bar-4-axios/0a3acf92/dist/nprogress.css" />


That's it when it comes to setup. Now we can implement the progress bar. We have to invoke (only once) the loadProgressBar() function, which optionally takes two arguments - config and instance. The properties of the configuration object can be seen here. The second argument is used to pass custom axios instance. For now, we'll skip both of those arguments to keep it as simple as possible.

...

<img id="codilime-logo" />

...

<script type="text/javascript">
  loadProgressBar();
  const url = 'http://codilime.com/image/path/logo.jpg';

  function downloadFile(url) {
    axios.get(url, {
      responseType: 'blob',
    })
      .then(response => {
        const fileReader = new window.FileReader();
        fileReader.readAsDataURL(response.data);
        fileReader.onload = () => {
          document.getElementById('codilime-logo').setAttribute('src', fileReader.result);
        };
      })
      .catch(error => {
        console.log(error)
      })
  }

  downloadFile(url);
</script>

The above code makes use of the FileReader API. It lets us read the contents of files, using File or Blob objects. FileReader.readAsDataURL() starts reading the contents of the specified blob, and once finished, the FileReader.result attribute contains an encoded string. We also make use of the load event, which fires when a read has been completed successfully. That's when we insert the result into the src attribute in our element.

Axios progress bar design was based on the angular-loading-bar. It uses the nprogress module which is responsible for displaying the loading bar in the browser.

Fetch

As usual, implementing the progress bar with Fetch API is not as easy. We don't have a package which provides us with a simple-to-use API, as is the case with Axios. Fear not, though - as you probably can guess, there is another low-level API that allows us to implement a progress bar. Sadly, with quite a bit more code.

We are going to use ReadableStream API. This interface represents a readable stream of byte data. Additionally, the ReadableStream instance is present on the body property of the Response object. We're going to use that stream of bytes to keep track of progress.

HTML file:

...

<div id="progress-bar" src="">Progress bar</div>
<img id="codilime-logo" />

...

JS file:

const el = document.getElementById('progress-bar');

fetch('http://codilime.com/image/path/logo.jpg')
  .then(response => {
    if (!response.ok) {
      throw Error(`HTTP error: ${response.status}`);
    }

    // Ensure ReadableStream is supported
    if (!response.body) {
      throw Error('ReadableStream API is not supported in this browser');
    }

    // The Content-Length header indicates the size of the message body, in bytes
    const contentLength = response.headers.get('content-length');
    if (!contentLength) {
      throw Error ('Content-Length response header unavailable');
    }
    
    const total = parseInt(contentLength, 10);
    let loaded = 0;

    return new Response(
      new ReadableStream({
        start(controller) {
          const reader = response.body.getReader();

          read();
          // read() function handles each data chunk
          function read() {
            reader.read().then(({ done, value }) => {
              // No more data to read
              if (done) {
                controller.close();
                return;
              }
              loaded += value.byteLength;
              progress({ loaded, total });
              // Get the data and send it to the browser via the controller
              controller.enqueue(value);
              read();
            }).catch(error => {
              console.error(error);
              controller.error(error);
            })
          }
        }
      })
    );
  })
  .then(response => response.blob())
  .then(data => {
    document.getElementById('codilime-logo').src = URL.createObjectURL(data);
  })
  .catch(error => {
    console.error(error);
  })

  function progress({ loaded, total }) {
    el.innerHTML = Math.round(loaded / total * 100) + '%';
  }

Okay, that's a lot of code - let's go through it step by step.

<div id="progress-bar" src="">Progress bar</div>
<img id="codilime-logo" />

In the HTML file, we create an img tag, the same as in the Axios example. In addition, we have to create a div which will be our progress bar, we don't use an external package with it, so we have to create it ourselves.

const el = document.getElementById('progress-bar');

fetch('http://codilime.com/image/path/logo.jpg')
  .then(response => {
    if (!response.ok) {
      throw Error(`HTTP error: ${response.status}`);
    }

    // Ensure ReadableStream is supported
    if (!response.body) {
      throw Error('ReadableStream API is not supported in this browser');
    }

    // The Content-Length header indicates the size of the message body, in bytes
    const contentLength = response.headers.get('content-length');
    if (!contentLength) {
      throw Error ('Content-Length response header unavailable');
    }

...

Then onto JS. We save our div element in the el variable. After that, we make a request. The standard procedure is to check whether there was any HTTP error, if the ReadableStream API is supported, and that the Content-Length header is present.

ReadableStream API is supported on all major browsers, as you can check via caniuse. Because of that, you can probably omit the second if statement in your implementation.

We add a third if statement because to access headers, the server must send a CORS header - Access-Control-Expose-Headers - "Access-Control-Expose-Headers: content-length". This response header allows a server to indicate which response headers should be made available to scripts running in the browser, in response to the cross-origin request.

Now, onto the returned response.

...

const total = parseInt(contentLength, 10);
let loaded = 0;

return new Response(
  new ReadableStream({
    start(controller) {

...

The ReadableStream constructor creates and returns a readable stream object from the given handlers. It takes in the underlayingSourceobject, which contains methods and properties that define how the constructed stream instance will behave. You can read about it more here.

In our case, the only thing we need is the start(controller) method. It's called immediately when the object is constructed. Its content should aim to access the stream source and set up the stream functionality. The controller property is either a ReadableStreamDefaultController or a ReadableByteStreamController, based on the type property.

...
      
const reader = response.body.getReader();

...

ReadableStream's getReader() method creates a reader and locks the stream to it. During that lock, no other reader can be acquired until this one is released.

...

read();
// read() function handles each data chunk
function read() {
  reader.read().then(({ done, value }) => {
    // No more data to read
    if (done) {
      controller.close();
      return;
    }
    loaded += value.byteLength;
    progress({ loaded, total });
    // Get the data and send it to the browser via the controller
    controller.enqueue(value);
    read();
  }).catch(error => {
    console.error(error);
    controller.error(error);
  })
}

...

In the read() function, we invoke the read() method on our reader, which returns a promise providing access to the next chunk in the stream's internal queue. If a chunk is available, the promise will be fulfilled with an object of the form:

{
  value: theChunk,
  done: false,
}

Otherwise, if the stream becomes closed, the promise will be fulfilled with an object of the form:

{
  value: undefined,
  done: true,
}

The only time the promise will be rejected, is when the stream shows an error.

...

// No more data to read
if (done) {
  controller.close();
  return;
}
loaded += value.byteLength;
progress({ loaded, total });
// Get the data and send it to the browser via the controller
controller.enqueue(value);
read();

...

If there is no more data to read, we close the associated stream using the close() method. Otherwise, we modify the div element using our progress function. After that, we use the enqueue() function which enqueues a given chunk in the associated stream.

...

console.error(error);
controller.error(error);

...

In the .catch clause, we use the error() method, which causes any future interactions with the associated stream to error.

Please keep in mind that this is just one way of implementing the progress bar using Fetch API. You can check out this and other implementations in this GitHub repo.

A wrap-up

It’s the same story as before. We have a choice. With the  high-level Axios API we have to use an additional package - the Axios Progress Bar.

On the other hand, we have the low-level Fetch API, where we can also implement a progress bar. What’s more, we can implement it in various different ways. It's definitely more code, but also, as usual, it offers more fine-grained control over what is happening at each stage of the data-handling. On top of that, we don't need any additional packages. It can all be done with just JavaScript and a few APIs.

Simultaneous requests

If you've made it through the progress bar section, you'll be glad to hear that both Axios and Fetch API provide easy to use ways of making multiple simultaneous requests. Let's see it in action.

Axios

In Axios, we create an array of requests. Each request is created the same way as before. Additionally, we use the axios.all method to make multiple HTTP requests:

const endpoints = [
  'http://codilime.com/endpoint1',
  'http://codilime.com/endpoint2',
  'http://codilime.com/endpoint3',
];

axios.all(
  endpoints.map(endpoint => axios.get(endpoint))
)
  .then(console.log)

We map through each of the endpoints, making an HTTP GET request to the current endpoint.

axios.all returns an array as a response, where each entry corresponds to an endpoint in our endpoints array. Because of that, we'll get the response for the endpoint http://codilime.com/endpoint1 at data[0], http://codilime.com/endpoint2 at data[1], etc.

Thankfully, Axios also provides the axios.spread method, which behaves similarly to the regular JavaScript spread operator - ...:

const endpoints = [
  'http://codilime.com/endpoint1',
  'http://codilime.com/endpoint2',
  'http://codilime.com/endpoint3',
];

axios.all(
  endpoints.map(endpoint => axios.get(endpoint))
)
  .then(axios.spread((res1, res2, res3) => {
    console.log(res1.data, res2.data, res3.data);
  }))

Fetch

When using Fetch API, we can achieve similar behavior using the Promise.all() method, and destructuring:

const endpoints = [
  'http://codilime.com/endpoint1',
  'http://codilime.com/endpoint2',
  'http://codilime.com/endpoint3',
];

Promise.all(
  endpoints.map(endpoint => fetch(endpoint))
)
  .then(async ([ res1, res2, res3 ]) => {
    const res1JSON = await res1.json();
    const res2JSON = await res2.json();
    const res3JSON = await res3.json();
    console.log(res1JSON, res2JSON, res3JSON);
  }))

If you're not familiar with Promise.all(), it takes in an iterable of promises (such as an array) and returns a single Promise, which resolves to an array of the results of the input promises.

A wrap-up

When it comes to making simultaneous requests, we've got two very similar solutions. One which leverages external library methods, and one which uses built-in JavaScript ones. However, the meaning and flow of both of those implementations are pretty much the same.

Axios is... a library

And as such, we depend on creators and maintainers to ensure security, updates, etc. That can be an issue, and it was (is?) with Axios. I recommend checking out this Reddit thread which points out several issues. I'll briefly go through some of them.

The thread is from 2017, but remains valid.

Vulnerability

In 2017, Snyk reported a vulnerability which was ignored by creators for over two years.

It took multiple people bombarding the project for the member to finally show up. After it was merged, it took another three weeks before it was released on NPM.

Members

Axios is, in theory, maintained by four people, at least that's how many are in the Axios organization. However, in reality, the project is maintained by one person. That's a pretty big project, used in a huge amount of projects, to be maintained just by a single person.

Project dead?

This short issue from 2019 indicates the problem which I mentioned above:

"Is the project dead? Are new maintainers sought?

- 97 open pull requests

- 411 open issues"

And you know what? A core member showed up, and posted the following comment:

"It's not dead, I just haven't been able to personally do as much on the project lately. We had a big issue with fixing configurations, which introduced breaking changes, that have halted things until that gets fixed.

So yes, if there are people willing to step up and help as maintainers, I welcome them!"

Great! After that, another post from a core member:

"y'all are AWESOME.

To anyone who wants to help, here are a few ideas I have:

Triage issues: I recently added issue templates to help auto-tag issues (and filter out actual bugs vs usage issues). There's a lot of noise for this project and I spend the majority of my time trying to filter through issues and wind up closing most of them with a simple "This doesn't seem like an Axios bug (many I can't even duplicate), I think X may be your issue, feel free to post on Gitter or Stack Overflow for help debugging your code". If you find a real bug that doesn't have example code, providing example code is a HUGE help. Bonus points if it's as simple as copy/pasting into Runkit with calls to an example API like JSON Placeholder.

PR Review: Not quite as noisy as issues, but this can still be a lot to go through. I really appreciate people who tag me in PRs that have high priority/fix known issues. Feel free to ping me if I don't respond after a few days. Currently, the focus is definitely getting things stable before focusing on new features or 1.0.0.

CI: Our CI is finicky - we often hit weird edge cases or issues that cause CI to break and that slows up the whole process. If we have a broken master branch, I can't release, plain and simple. So if you ever see that master is failing (or PRs are failing for issues not caused by the PR), any help there is massively appreciated.

I'm happy to give anyone access as needed. The only thing I'd like to hold onto is acting as the release manager to ensure consistency.

I plan on adding this info to the contributing doc along with my response templates for others to use and guidelines for how issues should be labeled, etc."


...it didn't take long before they disappeared again.
As of writing this article, there are:

  • 158 opened issues,
  • 37 opened pull requests.

This section isn't meant to complain about the authors of the project. It's rather meant to show you that there are some non-technical issues which you should take into consideration.

Conclusion

Axios is a great library that revolutionized working with HTTP requests. It was ahead of its time. However, since its initial release, there have been major changes to JavaScript. Fetch API is an amazing low-level API that allows you to create any functionality which exists in Axios, in exchange for additional "boilerplate" code.

Apart from the obvious high-level vs. low-level differences, and boilerplate, we’ve seen that an external library can introduce some risks which should be taken into consideration before making a choice.

After all, there isn't an objective grade with which we can say which solution is better. It all depends on your needs and preferences, which hopefully this article helped you to get to know. As such, I hope this comparison will help you in making a thoughtful and correct decision, suitable for your use case.

Michał

Michał Kuliński

Frontend Engineer