How to handle API errors in your web app using axios

Whenever you're making a backend API call with axios, you have to consider what to do with the .catch() block of your promise. Now, you might think that your API is highly available and it'll be running 24/7. You might think that the user workflow is pretty clear, your javascript is sane, and you have unit tests. So when you stare at the catch block when making requests using axios you might think - "Well... I'll just console.log it. That'll be fine."

axios.get('/my-highly-available-api').then(response => {
    // do stuff
})
.catch(err => {
    // what now?
    console.log(err);
})

But there are so many many many more things that are outside of your control that could throw errors when making API requests. And you probably don't even know that they're happening!

This post deals mainly with errors you see in the browser. Things can get pretty funny looking on the back end too - just take a look at 3 things you might see in your backend logs

Below are 3 types of errors that could appear, and how to handle it, when using axios.

Catching axios errors

Below is a snippet I've started including in a few JS projects.

axios.post(url, data).then(res => {
        // do good things
})
.catch(err => {
    if (err.response) {
      // client received an error response (5xx, 4xx)
    } else if (err.request) {
      // client never received a response, or request never left
    } else {
      // anything else
    }
})

Each condition is meant to capture a different type of error.

Checking error.response

If your error object contains a response field, that means your server responded with a 4xx/5xx error. Usually this is the error we're most familiar with, and is most straightforward to handle.

Do things like a show a 404 Not Found page/error message if your API returns a 404. Show a different error message if your backend is returning a 5xx or not returning anything at all. You might think your well-architected backend won't throw errors - but its a matter of when, not if.

Checking error.request

The second class of errors is where you don't have a response but there's a request field attached to the error. When does this happen? This happens when the browser was able to make a request, but for some reason, it didn't see a response.

This can happen if:

  • you're in a spotty network (think underground subway, or a building wireless network)
  • if your backend is hanging on each request and not returning a response on time
  • if you are making cross-domain requests and you're not authorized to make the request
  • if you're making cross-domain requests and you are authorized, but the backend API returns an error

One of the more common versions of this error had a message "Network Error" which is a useless error message. We have a front-end and a backend api hosted on different domains, so every backend API call is a cross-domain request.

Due to security constraints on JS in the browser, if you make an API request, and it fails due to crappy networks, the only error you'll see is "Network Error" which is incredibly unhelpful. It can mean anything from "your device doesn't have internet connectivity" to "your OPTIONS returned a 5xx" (if you make CORS requests). The reason for "Network Error" is described well here on this StackOverflow answer

All the other types of errors

If your error object doesn't have the response or request field on it, that means its not an axios error and theres likely something else wrong in your app. The error message + stack trace should help you figure out where its coming from.

How do you fix it?

Degrading the user experience

This all depends on your app. For the projects I work on, for each of the features that use those endpoints, we degrade the user experience.

For example, if the request fails, and the page is useless without that data, then we have a bigger error page that will appear and offer users a way out - which sometimes is only a "Refresh the page" button.

Another example, if a request fails for a profile picture in a social media stream, we can show a placeholder image and disable profile picture changes, along with a toaster message explaining why the "update profile picture" button is disabled. However, showing an alert saying "422 Unprocessable Entity" is useless to see as a user.

Spotty networks

The web client I work on is used in school networks which can be absolutely terrible. The availability of your backend barely has anything to do with it, the request sometimes fail to leave the school network.

To solve these types of intermittent network problems, we added in axios-retry. This solved a good amount of the errors we were seeing in production. We added this to our axios setup:

const _axios = require('axios')
const axiosRetry = require('axios-retry')

const axios = _axios.create()

// https://github.com/softonic/axios-retry/issues/87
const retryDelay = (retryNumber = 0) => {
    const seconds = Math.pow(2, retryNumber) * 1000;
    const randomMs = 1000 * Math.random();
    return seconds + randomMs;
};

axiosRetry(axios, {
    retries: 2,
    retryDelay,
    // retry on Network Error & 5xx responses
    retryCondition: axiosRetry.isRetryableError,
});

module.exports = axios;

We were able to see that 10% of our users (which are in crappy school networks) were seeing sporadic "Network Errors" and that dropped down to <2% after adding in automatic retries on failure.

chart showing count of Network Errors

^ This is a screenshot of our errors is the count of "Network Errors" as they appear in NewRelic and are showing <1% of requests are erroring out.

Which leads to my last point:

Add error reporting to your front-end

Its helpful to have front-end error/event reporting so that you know whats happening in prod before your users tell you. At my day job, we use NewRelic Browser (paid service - no affiliation, just a fan) to send error events from the front-end. So whenever we catch an exception, we log the error message, along with the stack trace (although thats sometimes useless with minified bundles), and some metadata about the current session so we can try to recreate it.

Other tools that fill this space are Sentry + browser SDK, Rollbar, and a whole bunch of other useful ones listed here

Wrapping up

If you get nothing else out of this, do one thing.

Go to your code base now, and review how you're handling errors with axios.

  • Check if you're doing automatic retries, and consider adding axios-retry if you aren't
  • Check that you're catching errors, and letting the user know that something has happened. axios.get(...).catch(console.log) isn't good enough.

So. How do you handle your errors? Let me know in the comments.

You might also like
Uncategorized

More Similar Posts

Menu