Ensure Immutable Headers Across Axios Instances
Managing HTTP headers across multiple Axios instances often leads to
unexpected side effects when shared configuration objects are mutated at
runtime. This article explains why header mutations happen across Axios
clients and demonstrates how to guarantee header immutability using base
configuration factories, Object.freeze(), deep cloning, and
custom request interceptors.
The Cause of Shared Header Mutation
Axios instances created with axios.create() can
inadvertently share state if you pass references to the same
configuration object. When you pass a plain JavaScript object as default
headers, JavaScript assigns it by reference. Consequently, modifying
instanceA.defaults.headers.common['Authorization'] can
alter the headers of instanceB if both instances reference
the same underlying object.
// Problematic pattern: Shared reference
const sharedHeaders = { 'X-Custom-Header': 'InitialValue' };
const clientA = axios.create({ headers: sharedHeaders });
const clientB = axios.create({ headers: sharedHeaders });
// Mutating clientA affects the shared object
clientA.defaults.headers['X-Custom-Header'] = 'MutatedValue';
console.log(clientB.defaults.headers['X-Custom-Header']); // Outputs 'MutatedValue'Strategy 1:
Freeze Base Configurations with Object.freeze()
To prevent direct runtime modification of your base headers, define
your default headers using Object.freeze(). For nested
header configurations, use a deep freeze helper to ensure nested
properties cannot be altered.
function deepFreeze(object) {
const propNames = Reflect.ownKeys(object);
for (const name of propNames) {
const value = object[name];
if ((value && typeof value === 'object') || typeof value === 'function') {
deepFreeze(value);
}
}
return Object.freeze(object);
}
const IMMUTABLE_BASE_HEADERS = deepFreeze({
'Content-Type': 'application/json',
'Accept': 'application/json',
'X-App-Version': '1.0.0'
});Strategy 2: Use Factory Functions with Deep Cloning
Never pass the same configuration reference to multiple
axios.create() calls. Use a factory function that generates
fresh, decoupled copies of your default configuration for every new
instance using structuredClone() or object spreading.
import axios from 'axios';
function createAxiosInstance(customConfig = {}) {
// Create a deep copy of the base headers
const baseHeaders = {
'Content-Type': 'application/json',
'Accept': 'application/json',
...(customConfig.headers || {})
};
const instance = axios.create({
...customConfig,
headers: structuredClone(baseHeaders)
});
return instance;
}
// Each instance has an isolated header context
const authClient = createAxiosInstance({
baseURL: 'https://api.example.com/auth'
});
const apiClient = createAxiosInstance({
baseURL: 'https://api.example.com/data'
});Strategy 3: Enforce Headers via Request Interceptors
If you must guarantee that certain headers cannot be overwritten—even
if code modifies instance.defaults.headers—use a request
interceptor. Interceptors execute right before the network dispatch and
can programmatically enforce non-negotiable headers.
function attachImmutableHeaderInterceptor(instance, lockedHeaders) {
instance.interceptors.request.use((config) => {
// Merge locked headers over any instance or request-level overrides
config.headers = Object.assign({}, config.headers, lockedHeaders);
return config;
}, (error) => {
return Promise.reject(error);
});
return instance;
}
const client = axios.create({ baseURL: 'https://api.example.com' });
attachImmutableHeaderInterceptor(client, {
'X-Security-Token': 'strictly-enforced-token',
'X-Environment': 'production'
});Summary Checklist for Header Isolation
- Avoid Shared Objects: Never pass the same headers object literal to multiple instances.
- Deep Clone Configs: Use
structuredClone()inside instance factory functions. - Freeze Static Constants: Wrap static base
configurations in
Object.freeze(). - Use Interceptors for Critical Headers: Enforce mandatory headers at the request execution layer so they cannot be removed or overwritten at the instance defaults layer.