Using axios interceptors in Frontend
Axios is the go-to library for managing HTTP requests in web development. While many developers have started using the native fetch
API, if your requirements are more complex and you need a reliable solution, Axios remains the top choice. It offers a range of built-in features that are either challenging or not feasible to achieve with fetch
.
Interceptors are such a feature in Axios that allows us to intercept HTTP requests or responses, making it possible to implement useful patterns that enhance the user experience. They function like middleware, being triggered before Axios resolves the request or response promise with .then()
or .catch()
. As you might expect, Axios provides two types of interceptors: one for requests and another for responses. Let's explore both types, using two common patterns in modern web development.
Before that, here is the basic structure of interceptors. Ref - Axios documentation
// request interceptor
axios.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
}, function (error) {
// Do something with request error
return Promise.reject(error);
});
// response interceptor
axios.interceptors.response.use(function (response) {
// Any status code that lie within the range of 2xx cause this function to trigger
// Do something with response data
return response;
}, function (error) {
// Any status codes that falls outside the range of 2xx cause this function to trigger
// Do something with response error
return Promise.reject(error);
});
Handling refresh token with request interceptor
When managing authentication with JWT tokens, it's common practice to use refresh tokens alongside access tokens to prevent users from needing to log in frequently. Since access tokens are short-lived for security reasons, they must be periodically refreshed using the refresh token. The Axios request interceptor is the ideal place to handle this process.
axios.interceptors.request.use(async function (config) {
// suppose we stored auth tokens in localStorage at the time of login
let { accessToken, expiresAt, refreshToken } = JSON.parse(localStorage.getItem("auth"));
const hasAccessTokenExpired = new Date().getTime() >= expiresAt;
if (hasAccessTokenExpired) {
// refresh the access token
try {
const response = await axios.post("/api/auth/refresh-token", {
refreshToken,
});
// store the new tokens in localStorage
localStorage.setItem("auth", JSON.stringify(response.data));
accessToken = response.data.accessToken;
} catch (err) {
throw new Error("Invalid refresh token!");
}
}
config.headers.Authorization = `Bearer ${accessToken}`;
return config;
},
(error) => {
return Promise.reject(error);
});
Retry on error with response interceptor
A request may occasionally fail due to a temporary server issue, and retrying it could resolve the problem. In such cases, an automatic retry mechanism can be useful. However, it's important to ensure that retries are only attempted for errors that are likely to be recoverable, such as 408, 429, 500, 502, 503, and 504. Otherwise, it will be just a waste of resources.
axios.interceptors.response.use(
function (response) {
// if the request succeeded, simply return the response
return response;
},
async function (error) {
// if the request failed, retry once more
const { config, response } = error;
// Only retry for 429, 500, 502, 503 and 504 status codes
if (!response || [429, 500, 502, 503, 504].includes(response.status)) {
return Promise.reject(error);
}
// Avoid infinite retry loops
if (config._retry) {
return Promise.reject(error);
}
config._retry = true;
try {
// Retry the original request
return await axios(config);
} catch (retryError) {
return Promise.reject(retryError);
}
}
);