Axios Request and Response Interceptors
Axios interceptors come in handy when we need to track, register, or work with:
- Requests before leaving
- Responses before arriving
- Both 1 and 2
Thanks to interceptors, we can pass our own handlers/callbacks for the following cases:
- Before launching a request
- Catching an error at HTTP request launch
- Arrival of a response
- Catching an error at response arrival
This is an example of how we can use them:
// handlers for the request launch and for catching request launch error
axios.interceptors.request.use(
(config) => {
console.log('We are now preparing to launch the request!')
return config;
},
(error) => Promise.reject(error),
);
// handlers for response interceptor and error response interceptor
axios.interceptors.response.use(
(response) => {
console.log('We received the response!');
return response;
},
(error) => {
if (error.response.status === 403)
return Promise.reject(error);
}
},
Each Axios Instance can have its custom configuration and request and response interceptors. This can be very useful when the architecture of our application enforces/allows each of our services to use their own Axios Instance. This way, each service can have an Axios Instance with custom configuration and custom request/response interceptors.
This is how an Axios Instance factory utility function could look like in TypeScript:
export const createAxiosWithInterceptors = (
requestConfig: AxiosRequestConfig = {},
requestInterceptorHandlers: Partial<RequestInterceptorHandlers> = DefaultRequestInterceptor,
responseInterceptorHandlers: Partial<ResponseInterceptorHandlers> = DefaultResponseInterceptor,
) => {
const axiosInstance = axios.create(requestConfig);
axiosInstance.interceptors.request.use(
requestInterceptorHandlers.requestConfigHandler,
requestInterceptorHandlers.requestErrorHandler,
);
axiosInstance.interceptors.response.use(
responseInterceptorHandlers.responseHandler,
responseInterceptorHandlers.responseErrorHandler,
);
return axiosInstance;
};
where we can define our types and default interceptors as follows:
type RequestConfigHandler = (config: AxiosRequestConfig) => AxiosRequestConfig;
type RequestErrorHandler = (error: AxiosError) => Promise<AxiosError>;
type RequestInterceptorHandlers = {
requestConfigHandler: RequestConfigHandler;
requestErrorHandler: RequestErrorHandler;
};
type ResponseHandler = (response: AxiosResponse) => AxiosResponse;
type ResponseErrorHandler = (error: AxiosError) => Promise<AxiosError>;
type ResponseInterceptorHandlers = {
responseHandler: ResponseHandler;
responseErrorHandler: ResponseErrorHandler;
};
const DefaultResponseInterceptor = {
responseHandler: (response: AxiosResponse) => response,
responseErrorHandler: (error: AxiosError) => Promise.reject(error),
};
const DefaultRequestInterceptor = {
requestConfigHandler: (config: AxiosRequestConfig) => config,
requestErrorHandler: (error: AxiosError) => Promise.reject(error),
};
In each of our services we can then use our factory method as follows:
// axios instance with custom request config received through constructor, custom request interceptor and default response interceptor
protected http: AxiosInstance = createAxiosWithInterceptors(
this.requestConfig,
{
requestConfigHandler: (config) => {
console.log();
return config;
},
},
);