Axios Interceptors: The Ultimate Guide to Mastering API Workflows

Introduction: The Power of Axios Interceptors
Axios interceptors are a cornerstone of efficient API management in modern web development. They act as middleware, allowing developers to intercept, modify, and track HTTP requests and responses before they reach your application logic. Whether you’re building a small React app or a large-scale enterprise system, interceptors help you:
- Centralize repetitive tasks (e.g., adding authentication headers).
- Handle errors globally (e.g., redirecting users after a 401 error).
- Transform data (e.g., parsing responses or logging metrics).
This guide dives deep into interceptors, covering everything from basic setup to advanced patterns like token refresh and retry logic. By the end, you’ll have the tools to streamline your API workflows and solve common pain points.
Table of Contents
What Are Axios Interceptors?
Interceptors are functions that run automatically during the lifecycle of an HTTP request:
- Request Interceptors : Execute before a request is sent to the server.
- Response Interceptors : Execute after a response is received.
They’re particularly useful for decoupling logic from your API calls, ensuring cleaner code and easier maintenance.
Key Features For Axios Interceptors
- Global Control : Apply changes to all requests/responses.
- Error Handling : Catch and manage errors in one place.
- Data Transformation : Modify payloads or responses on the fly.
- Performance Tracking : Measure API latency and log metrics.
Core Concepts & Execution Flow
Request Interceptors
These run before the request is sent. Common use cases:
- Adding headers (e.g., Authorization, Content-Type).
- Modifying request data (e.g., stringifying JSON).
- Logging request details for debugging.
Example Of Request Interceptors :
axios.interceptors.request.use(
(config) => {
// Add auth token from localStorage
const token = localStorage.getItem('auth_token');
if (token) config.headers.Authorization = `Bearer ${token}`;
return config;
},
(error) => {
// Handle request errors (e.g., network issues)
console.error('Request failed:', error);
return Promise.reject(error);
}
);
Response Interceptors
These run after the response is received. Common use cases:
- Handling non-2xx status codes (e.g., 401, 500).
- Parsing response data (e.g., extracting response.data).
- Logging response times or errors.
Example Of Response Interceptors :
axios.interceptors.response.use(
(response) => {
// Simplify response by returning only data
return response.data;
},
(error) => {
if (error.response?.status === 401) {
// Redirect to login for unauthorized access
window.location.href = '/login';
}
return Promise.reject(error);
}
);
Advanced Use Cases Of Interceptors
Token Refresh with Retry Logic
When a token expires, interceptors can automatically refresh it and retry the failed request:
// Create an axios instance
const api = axios.create({ baseURL: '/api' });
// Track ongoing refresh requests
let isRefreshing = false;
let failedQueue = [];
// Response interceptor for token refresh
api.interceptors.response.use(
(response) => response,
async (error) => {
const originalRequest = error.config;
if (error.response?.status === 401 && !originalRequest._retry) {
if (isRefreshing) {
// Queue the request until the token is refreshed
return new Promise((resolve, reject) => {
failedQueue.push({ resolve, reject });
})
.then(() => api(originalRequest))
.catch((err) => Promise.reject(err));
}
originalRequest._retry = true;
isRefreshing = true;
try {
// Call your auth API to refresh the token
const { token } = await refreshToken();
localStorage.setItem('auth_token', token);
// Retry all queued requests
failedQueue.forEach((p) => p.resolve());
return api(originalRequest);
} catch (err) {
failedQueue.forEach((p) => p.reject(err));
localStorage.removeItem('auth_token');
window.location.href = '/login';
return Promise.reject(err);
} finally {
isRefreshing = false;
failedQueue = [];
}
}
return Promise.reject(error);
}
);
Global Error Handling
Centralize error logging and user feedback:
axios.interceptors.response.use(
(response) => response,
(error) => {
// Log errors to a monitoring service like Sentry
Sentry.captureException(error);
// Show user-friendly messages
if (error.response?.status >= 500) {
toast.error('Something went wrong. Please try again later.');
}
return Promise.reject(error);
}
);
Performance Tracking
Measure API latency to optimize performance:
axios.interceptors.request.use((config) => {
config.metadata = { startTime: new Date() };
return config;
});
axios.interceptors.response.use(
(response) => {
const endTime = new Date();
const duration = endTime - response.config.metadata.startTime;
console.log(`API call to ${response.config.url} took ${duration}ms`);
return response;
},
(error) => {
// Track failed requests
console.error(`API call failed after ${duration}ms`);
return Promise.reject(error);
}
);
Best Practices
- Organize Interceptors Separately: Create a dedicated axiosConfig.js file to keep your codebase clean.
- Avoid Silent Failures: Always propagate errors with Promise.reject(error) to prevent unhandled rejections.
- Test Edge Cases: Simulate scenarios like Expired tokens, Network outages, Server timeouts. For More information read our blog about how to handle Axios Common Errors.
- TypeScript Integration: Define types for interceptors to avoid runtime errors.
Troubleshooting Common Issues
- Interceptors Stop Working After Updates: Newer Axios versions may require interceptors to be added to the instance , not the global axios object.
- Memory Leaks: Avoid adding interceptors inside components. Instead, register them once during app initialization.
- Conflicting Interceptors: Use axios.interceptors.request.eject() to remove outdated interceptors.
Real-World Examples
Logging Interceptor
Track all API interactions for debugging:
axios.interceptors.request.use((config) => {
console.log('Request:', config.method.toUpperCase(), config.url);
return config;
});
axios.interceptors.response.use(
(response) => {
console.log('Response:', response.status, response.config.url);
return response;
},
(error) => {
console.error('Error:', error.message, error.config.url);
return Promise.reject(error);
}
);
Dynamic Base URL
Switch environments (dev/staging/production) dynamically:
axios.interceptors.request.use((config) => {
config.baseURL = process.env.NODE_ENV === 'production'
? 'https://api.production.com'
: 'https://api.dev.com';
return config;
});
Conclusion: Elevate Your API Workflow
Axios interceptors are a Swiss Army knife for API management. By mastering them, you’ll write cleaner code, reduce redundancy, and handle edge cases with ease. Whether you’re debugging errors, optimizing performance, or securing requests, interceptors provide a centralized solution.
FAQs
How do I handle 401 Unauthorized errors globally with interceptors?
Use a response interceptor to check for 401 status codes. When detected, redirect users to a login page or trigger a token refresh flow. Ensure you propagate the error using Promise.reject() to avoid silent failures.
Can interceptors automatically retry failed requests (e.g., expired tokens)?
Yes. Implement a retry mechanism in the response interceptor. Track failed requests, refresh the token (if applicable), and retry the original request. Use a queue to manage concurrent retries and avoid race conditions.
Can interceptors track API performance or latency?
Yes. Use a request interceptor to record start times and a response interceptor to calculate duration. Log metrics to monitor performance bottlenecks.
Can interceptors modify headers for all requests?
Yes. Use a request interceptor to add headers (e.g., authentication tokens) to every outgoing request. Fetch tokens from secure storage (e.g., localStorage) dynamically.