Centralizing API Observability with Axios Interceptors
Stop repeating try/catch blocks. Learn how to use Axios interceptors to centralize request logging, measure API latency, and normalize error responses across your application.
14 Oct 2026, 13:08 UTC

The Problem: Repetitive Boilerplate in Service Layers
In many JavaScript applications, API calls are wrapped in repetitive try/catch blocks that handle the same logging and error-parsing logic. When you need to add a correlation ID to every request or normalize how 404s and 500s are reported to the UI, you are forced to update every single service function. This duplication obscures the actual business logic and makes it easy to miss logging for specific endpoints.
The Solution: Interceptor-Based Middleware
Axios interceptors act as middleware for your HTTP requests. They allow you to define a global pipeline that processes every outgoing request and every incoming response. By centralizing this logic, you can ensure that latency tracking, authentication headers, and error normalization happen consistently across the entire application without modifying individual call sites.
Implementing a Centralized API Instance
To avoid polluting the global Axios object, create a dedicated instance. This ensures that your interceptors only apply to your specific API backend and not to third-party requests (like an external analytics ping).
import axios, { InternalAxiosRequestConfig, AxiosResponse } from 'axios';
// Define a custom interface to extend the Axios config for timing
interface CustomRequestConfig extends InternalAxiosRequestConfig {
metadata?: {
startTime?: number;
};
}
const api = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
});
// Request Interceptor: Log outgoing calls and timestamp them
api.interceptors.request.use(
(config) => {
const customConfig = config as CustomRequestConfig;
customConfig.metadata = { startTime: Date.now() };
console.log(`[API Request] ${customConfig.method?.toUpperCase()} ${customConfig.url}`);
return customConfig;
},
(error) => Promise.reject(error)
);
// Response Interceptor: Calculate latency and normalize errors
api.interceptors.response.use(
(response: AxiosResponse) => {
const config = response.config as CustomRequestConfig;
const startTime = config.metadata?.startTime;
const duration = startTime ? Date.now() - startTime : null;
console.log(`[API Response] ${response.status} ${config.url} - ${duration ? duration + 'ms' : 'unknown'}`);
return response;
},
(error) => {
// Normalize the error shape before it reaches the catch block
const normalizedError = {
message: error.response?.data?.message || error.message || 'An unexpected error occurred',
status: error.response?.status,
url: error.config?.url,
timestamp: new Date().toISOString(),
};
console.error(`[API Error]`, normalizedError);
return Promise.reject(normalizedError);
}
);
export default api;
Using the Instance in Components
Because the interceptors handle the logging and error shaping, the calling code remains lean. You no longer need to manually parse the Axios error object to find the status code or message.
import api from './api';
async function fetchUserProfile(userId: string) {
try {
const { data } = await api.get(`/users/${userId}`);
return data;
} catch (err: any) {
// err is now the normalized object: { message, status, url, timestamp }
alert(`Error ${err.status}: ${err.message}`);
throw err;
}
}
Trade-offs and Engineering Constraints
While interceptors simplify the service layer, they introduce a layer of indirection. If a response interceptor modifies the data or fails to return the response object, it can cause "silent" failures where the calling function receives undefined instead of the expected data.
Additionally, be mindful of the interceptor chain order. Axios executes request interceptors in reverse order of registration and response interceptors in the order they were added. If you have multiple interceptors modifying the same config object, the last one registered for requests (and first for responses) takes precedence.
Verification Steps
To verify this implementation is working as expected, perform the following checks:
- Request Validation: Trigger any API call and verify that the
[API Request]log appears in the console before the network request is visible in the browser's Network tab. - Latency Check: Compare the logged duration (e.g.,
150ms) with the "Timing" tab in Chrome DevTools. They should be closely aligned, though the interceptor includes a small amount of overhead. - Error Normalization: Force a 404 error by calling a non-existent endpoint. Verify that the
catchblock receives an object with the specific keysmessage,status, andurl, rather than the raw Axios error object.
Practical Closing
Centralizing your API logic via interceptors transforms your network layer from a series of scattered calls into a managed pipeline. To implement this, start by creating a single Axios instance for your project and migrating one service at a time to use that instance. Once your logging and error shapes are consistent, you can easily add further global logic—such as automatically attaching JWT tokens to headers—without touching your UI components.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.