Transform camelCase to snake_case in Axios

This article explains how to build an automated middleware pipeline in Axios using request and response interceptors to transform JavaScript camelCase objects into backend-friendly snake_case payloads, and automatically convert incoming snake_case responses back to camelCase.

1. Create the Key Transformation Helpers

To handle nested objects and arrays reliably, create recursive helper functions that convert object keys between casing conventions.

function camelToSnake(str) {
  return str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
}

function snakeToCamel(str) {
  return str.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
}

export function keysToSnake(input) {
  if (Array.isArray(input)) {
    return input.map((item) => keysToSnake(item));
  }
  
  if (input !== null && typeof input === 'object' && !(input instanceof FormData) && !(input instanceof Date)) {
    return Object.keys(input).reduce((acc, key) => {
      const snakeKey = camelToSnake(key);
      acc[snakeKey] = keysToSnake(input[key]);
      return acc;
    }, {});
  }
  
  return input;
}

export function keysToCamel(input) {
  if (Array.isArray(input)) {
    return input.map((item) => keysToCamel(item));
  }
  
  if (input !== null && typeof input === 'object' && !(input instanceof Date)) {
    return Object.keys(input).reduce((acc, key) => {
      const camelKey = snakeToCamel(key);
      acc[camelKey] = keysToCamel(input[key]);
      return acc;
    }, {});
  }
  
  return input;
}

2. Configure Axios Interceptors

Axios interceptors function as middleware that process requests before they are sent and responses before they reach application logic.

Create a dedicated Axios instance and attach the transformation logic to the request and response interceptor pipelines:

import axios from 'axios';
import { keysToSnake, keysToCamel } from './caseTransformers';

const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  headers: {
    'Content-Type': 'application/json',
  },
});

// Request Interceptor: Transform camelCase to snake_case
apiClient.interceptors.request.use(
  (config) => {
    // Transform JSON request payload body
    if (config.data && !(config.data instanceof FormData)) {
      config.data = keysToSnake(config.data);
    }

    // Transform URL query parameters
    if (config.params) {
      config.params = keysToSnake(config.params);
    }

    return config;
  },
  (error) => Promise.reject(error)
);

// Response Interceptor: Transform snake_case back to camelCase
apiClient.interceptors.response.use(
  (response) => {
    if (response.data && typeof response.data === 'object') {
      response.data = keysToCamel(response.data);
    }
    return response;
  },
  (error) => {
    if (error.response && error.response.data && typeof error.response.data === 'object') {
      error.response.data = keysToCamel(error.response.data);
    }
    return Promise.reject(error);
  }
);

export default apiClient;

3. Usage Example

With the middleware pipeline active, you can send and consume standard JavaScript camelCase properties without manual mapping:

import apiClient from './apiClient';

async function createUser() {
  // Input using camelCase
  const payload = {
    firstName: 'Jane',
    lastName: 'Doe',
    contactDetails: {
      emailAddress: 'jane.doe@example.com',
      postalCode: '10001'
    }
  };

  // Sent over the wire as:
  // { "first_name": "Jane", "last_name": "Doe", "contact_details": { "email_address": "...", "postal_code": "..." } }
  const response = await apiClient.post('/users', payload, {
    params: { sendWelcomeEmail: true } // Query parameter becomes: ?send_welcome_email=true
  });

  // Response with backend snake_case keys is parsed back to camelCase
  console.log(response.data.userId); 
}