Interceptors in Axios

How to use request and response Interceptors in Axios to make cool things happen with ease

Published: 26 January 2020

axios http calls asynchronous interceptors error handling 

Basics


Consider a simple AJAX call like so:

// index.js
axios
  .get("https://reqres.in/api/users/2")
  .then(res => console.log(res))
  .catch(err => console.log(err))

Now how can we achieve the following:

  • Add different access/refresh tokens based on the URL you are calling
  • After a call fails (let's say your server is down), try to run the same call one more time but with new data (maybe your backup/proxy server)

Enter Interceptors


Interceptors are functions that can modify the data associated with our outgoing HTTP request or incoming response and always run before a request is sent out or a response is let in.

They are of 2 types:

(1) Request Interceptor

(2) Response Interceptor

General structure looks like so:

//Request Interceptor
const requestInterceptor = axios.interceptors.request.use(
  configFunction, // has info about our request
  errorFunction // handles errors
)

//Response Interceptor
const responseInterceptor = axios.interceptors.response.use(
  onSuccessFunction, // has successful response object
  onErrorFunction // has any errors
)

How to use them


We can either attach them to our global axios instance (if we have one) or attach them to axios before making a call.

Let's say we would like attach a different token based on the URL being called:

globalAxios.interceptors.request.use(
  config =>
    config.url.includes("/ai")
      ? { ...config, headers: { "X-custom": "lalala" } }
      : config,
  error => Promise.reject({ error: "could not send the request" })
)

globalAxios.get('/intelligence/ai')....

NOTE: Always remember to return the config if you want the request to go through.

If you need completely different Interceptors based on certain requests, its always advisable to create fresh axios instances and assign them the interceptors as needed.

Now an example of response interceptors. Let's says we would like to update the URL if our request has failed the first time around:

axios.interceptors.response.use(
  response => response,
  error => {
    if(error.response.status===404) {
      error.config.url = '/this/is/new/url/to/call'
      return axios(error.config);
    }
    return Promise.reject(err);
  }
)

globalAxios.get('/intelligence/ai')....

error.config contains our original request and we can directly pass it axios to re-trigger the original request with all the params in place.

But there is an issue with the above request - what if our second url also results in a reject promise with status 404?

In that case, we will be trapped in an infinite loop which will go on forever.

This is where axios custom properties come to our rescue.

Custom Properties in Axios instance


Just like any other in-built properties inside axios instance, we can also define our own custom properties which will then be accessible inside our interceptors to use:

axios({
  method: "get", //in-built
  url: "some/fancy/url", //in-built
  count: 0, //custom
  retries: 3, //custom
})

count will be used to keep track of the number of times a call has been made already.

retries will be used to repeat the call that many number of times

Let's see this in action, following from our example above:

//Lets assume we would like to make the call to the same URL 3 times before throwing an error

axios.interceptors.response.use(
  response => response,
  error => {
    const { count, retries } = error.config // extract count and retries
    if (error.response.status === 404 && count < retries) {
      error.config.count += 1 // update count
      return axios(error.config)
    }
    return Promise.reject(err)
  }
)

Accessing Data properties


In some cases, when the request fails, we may want to update the data we originally sent. This can be easily done this with error.config.data, however there is a gotcha.

Consider the following AJAX call which fails since old and new passwords are the same.

axios({
  method: "post",
  url: "https://reqres.in/api/register",
  data: {
    email: "xyz@gmail.com",
    oldPassword: "old-password",
    newPassword: "old-password",
  },
  count: 0,
  retries: 2,
})
  .then(res => console.log("success", res))
  .catch(err => console.log("new password same as old one"))

And let's say you want to provide a default new password instead of throwing an error. You might try to do the following:

axios.interceptors.response.use(
  res => res,
  err => {
    console.log("error config", error.config)
    error.config.data.newPassword = "new-password"
    return axios(error.config)
  }
)

The update to data object won't happen and no error will be thrown either.

If we check the console, we will find that error.config.data is actually a stringified object, so must use JSON.parse first before accessing/updating any of its properties.

axios.interceptors.response.use(
  res => res,
  err => {
    if (err.config.count < err.config.retries) {
      err.config.data = {
        ...JSON.parse(err.config.data),
        newPassword: "new-password",
      }
      err.config.count += 1
      return axios(err.config)
    }
    return Promise.reject(err)
  }
)

Remove an Interceptor


const requestInterceptor = axios.interceptors.request.use(func1, func2)
axios.interceptors.request.eject(requestInterceptor)

Axios Documentation on Interceptors

Example of Redirection in ReactJS using Interceptors

Full Guide to using Axios