Custom Axios Response Data Transformation Pipelines

This guide explains how to configure custom response data transformation pipelines in the Axios HTTP client. You will learn how to leverage the transformResponse configuration option and Axios interceptors to sanitize, normalize, and manipulate incoming server payloads before they reach your application's business logic.

Using the transformResponse Property

Axios provides a built-in transformResponse option that accepts an array of functions. Each function receives the response data and headers, passes its output to the next function in the pipeline, and returns the final transformed data.

By default, Axios includes a single transformer that parses JSON strings into JavaScript objects:

axios.defaults.transformResponse = [
  function (data) {
    return JSON.parse(data);
  }
];

Building a Multi-Step Transformation Pipeline

When defining custom pipelines, you can chain multiple functions to handle tasks like string decoding, property renaming, or date parsing.

import axios from 'axios';

// 1. Transformer to parse ISO date strings into Date objects
const parseDates = (data) => {
  if (typeof data !== 'object' || data === null) return data;

  for (const key of Object.keys(data)) {
    const value = data[key];
    const isoDateFormat = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/;
    
    if (typeof value === 'string' && isoDateFormat.test(value)) {
      data[key] = new Date(value);
    } else if (typeof value === 'object') {
      parseDates(value);
    }
  }
  return data;
};

// 2. Transformer to standardize API response wrappers
const unwrapData = (data) => {
  return data && data.payload ? data.payload : data;
};

// Create an Axios instance with the transformation pipeline
const apiClient = axios.create({
  baseURL: 'https://api.example.com',
  transformResponse: [
    // Retain default JSON parsing first
    ...axios.defaults.transformResponse,
    // Add custom transformers sequentially
    unwrapData,
    parseDates,
  ],
});

export default apiClient;

Configuring Transformations at Different Scopes

You can apply response transformation pipelines at the global level, instance level, or per-request level.

1. Per-Request Configuration

Override or append transformations for specific API calls:

apiClient.get('/users', {
  transformResponse: [
    ...axios.defaults.transformResponse,
    (data) => data.map(user => ({ ...user, fullName: `${user.firstName} ${user.lastName}` }))
  ]
});

2. Global Defaults

Apply a pipeline across all Axios calls in your application:

axios.defaults.transformResponse = [
  ...axios.defaults.transformResponse,
  (data) => {
    // Custom global transformation
    return data;
  }
];

transformResponse vs. Response Interceptors

While transformResponse is ideal for synchronous data manipulation on successful responses, Axios interceptors provide broader control.

Asynchronous Data Transformations with Interceptors

If your transformation pipeline requires asynchronous operations (such as decrypting data or making secondary lookup requests), use an interceptor instead:

apiClient.interceptors.response.use(async (response) => {
  if (response.data?.encrypted) {
    response.data = await decryptPayload(response.data.payload);
  }
  return response;
}, (error) => {
  return Promise.reject(error);
});

Best Practices