Normalize Data with Axios Response Interceptors
This article explains how to leverage Axios response interceptors to
automatically normalize API response data before it reaches your
application logic. By attaching a transformation layer directly to an
Axios instance, you can seamlessly convert key formats (such as
snake_case to camelCase), flatten nested
structures, or reshape relational entities across all incoming HTTP
requests without repetitive code in your services.
Understanding Axios Interceptors
Axios interceptors are middleware functions that you can attach to an
Axios instance to inspect or modify HTTP requests before they are sent,
or HTTP responses before they are handled by then or
catch blocks.
The axios.interceptors.response.use() method accepts two
callback functions:
- An onFulfilled handler that receives the
responseobject when a request succeeds (status code in the2xxrange). - An onRejected handler that receives the
errorobject when a request fails.
Creating a Normalization Function
Data normalization involves standardizing incoming payloads to match
your frontend data structures. A common use case is transforming backend
snake_case properties into frontend-friendly
camelCase properties.
// Utility function to convert snake_case keys to camelCase recursively
function toCamelCase(str) {
return str.replace(/([-_][a-z])/gi, ($1) =>
$1.toUpperCase().replace('-', '').replace('_', '')
);
}
function normalizeKeys(data) {
if (Array.isArray(data)) {
return data.map((item) => normalizeKeys(item));
}
if (data !== null && typeof data === 'object' && !(data instanceof Date)) {
return Object.keys(data).reduce((acc, key) => {
const camelKey = toCamelCase(key);
acc[camelKey] = normalizeKeys(data[key]);
return acc;
}, {});
}
return data;
}Attaching the Normalization Interceptor
Create a dedicated Axios instance and register the normalization
function inside the response interceptor. Modify
response.data and return the modified response
object.
import axios from 'axios';
// 1. Create a custom Axios instance
const apiClient = axios.create({
baseURL: 'https://api.example.com',
timeout: 10000,
headers: {
'Content-Type': 'application/json',
},
});
// 2. Add the response interceptor
apiClient.interceptors.response.use(
(response) => {
// Check if the response contains data and normalize it
if (response.data) {
response.data = normalizeKeys(response.data);
}
return response;
},
(error) => {
// Optionally normalize error response bodies as well
if (error.response && error.response.data) {
error.response.data = normalizeKeys(error.response.data);
}
return Promise.reject(error);
}
);
export default apiClient;Consuming Normalized Data
When using the configured apiClient, components and
services receive the normalized data automatically:
import apiClient from './apiClient';
async function fetchUserProfile(userId) {
// Backend returns: { user_id: 101, first_name: "Jane", created_at: "2024-01-01" }
const response = await apiClient.get(`/users/${userId}`);
// Normalized output: { userId: 101, firstName: "Jane", createdAt: "2024-01-01" }
return response.data;
}Best Practices for Interceptor Normalization
- Use Isolated Instances: Apply interceptors to
specific
axios.create()instances rather than the globalaxiosobject to avoid unintended side effects across third-party libraries. - Preserve Binary and Blob Data: Ensure your
normalization logic skips non-JSON payloads such as
Blob,ArrayBuffer, orFormData. - Handle Schema-Based Entities: For complex
relational state (like Redux or Pinia stores), you can integrate schema
libraries such as
normalizrinside the interceptor to convert nested API models into normalized collections keyed by ID.