Axios Interceptors Demystified: Header Injection, Logging, and Error Handling in One Flow
Learn how to use Axios interceptors for header injection, logging, and error handling. A step‑by‑step guide with a concrete example, trade‑offs, and a cleanup checklist.
15 Aug 2026, 15:46 UTC

Why Interceptors Matter
When you build an API‑heavy application, you often need to tweak every request or response in the same way—add auth tokens, normalize data, or log traffic. Axios interceptors let you do that without scattering code across your service layer. They sit between your application code and the network, altering config before a request is sent or response before it reaches your await statement.
Instance vs. Global Interceptors
Axios can be used in two ways:
- Global –
axios.interceptorsattaches to the default Axios instance used by all imports. Good for one‑off tweaks like a global logger. - Per‑instance –
const api = axios.create({ baseURL: '/api' })creates a new instance. Interceptors added toapi.interceptorsonly affect that instance, keeping side effects isolated.
In long‑running apps (single‑page apps, Node services), global interceptors that never get ejected can accumulate and leak memory. Use per‑instance interceptors when you need isolation or token rotation.
How Interceptors Execute
When a request is made, Axios runs request interceptors in the order they were added. The modified config is passed to the next interceptor, and finally to the network layer. For responses, the order is reversed: the last added response interceptor sees the raw response first, then passes it to the previous one.
Because order matters, place generic logic (e.g., header injection) before more specific ones (e.g., error transformation) if you want the latter to see the final request shape.
Example: Token Injection, Logging, and Error Normalization
Below is a minimal but complete example that shows:
- Adding a JWT to every request header.
- Logging the status code of each response.
- Transforming non‑2xx responses into a consistent error shape.
// apiClient.js
import axios from 'axios';
// 1. Create a dedicated instance
const api = axios.create({ baseURL: '/api' });
// 2. Request interceptor – inject auth header
const authInterceptor = api.interceptors.request.use(
config => {
const token = localStorage.getItem('jwt');
if (token) {
config.headers['Authorization'] = `Bearer ${token}`;
}
return config;
},
error => Promise.reject(error)
);
// 3. Response interceptor – log status and normalise errors
api.interceptors.response.use(
response => {
console.log('API response status:', response.status);
// Return data unchanged for normal success
return response;
},
error => {
// Axios turns non‑2xx status codes into an error
const { response } = error;
const payload = {
status: response?.status ?? 500,
message: response?.data?.message ?? 'Unknown error',
path: response?.config?.url
};
return Promise.reject(payload);
}
);
export default api;
Usage:
import api from './apiClient';
async function fetchUsers() {
try {
const res = await api.get('/users');
console.log('Users:', res.data);
} catch (err) {
// err is the normalised object from the interceptor
console.error('API error:', err);
}
}
fetchUsers();
Cleaning Up: Ejecting Interceptors
Interceptors return an id you can later pass to interceptors.eject(id). This is crucial when you create short‑lived instances (e.g., in tests) or when you need to rotate tokens and remove stale interceptors.
// Remove the auth interceptor when logging out
api.interceptors.request.eject(authInterceptor);
Trade‑Offs and Limitations
- Memory leaks – Global interceptors that are never ejected stay in memory for the app’s lifetime. Use per‑instance or eject on logout.
- Ordering surprises – If a later request interceptor overwrites a header set earlier, the earlier logic becomes ineffective. Keep a clear order and document it.
- Error masking – Throwing inside an interceptor will replace the original error. Always preserve the original error object or at least its status and message.
Actionable Checklist
- Decide whether the interceptor should be global or per‑instance.
- Add request interceptors first (e.g., auth, content‑type). Then add response logic.
- Return the interceptor
idif you might need to eject it later. - Test the flow: use a tool like Chrome DevTools or
npx axios-mock-adapterto verify headers and transformed responses. - Document the order and purpose of each interceptor so future maintainers understand the pipeline.
By treating interceptors as a pipeline rather than ad‑hoc tweaks, you keep your API layer clean, predictable, and easy to test.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.