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

  1. Avoid Shared Objects: Never pass the same headers object literal to multiple instances.
  2. Deep Clone Configs: Use structuredClone() inside instance factory functions.
  3. Freeze Static Constants: Wrap static base configurations in Object.freeze().
  4. 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.