---
title:
  "Automating Case Conversion in Axios for Seamless Frontend-Backend Integration"
description:
  "Learn why we decided to automatically case convert request and response data
  and how we did it."
canonical_url: "https://www.bigbinary.com/blog/axios-case-conversion"
markdown_url: "https://www.bigbinary.com/blog/axios-case-conversion.md"
---

# Automating Case Conversion in Axios for Seamless Frontend-Backend Integration

Learn why we decided to automatically case convert request and response data and
how we did it.

- Author: Ajmal Noushad
- Published: March 12, 2024
- Categories: neeto, React, Javascript

In the world of web development, conventions often differ between backend and
frontend technologies. This becomes evident when comparing variable naming case
conventions used in Ruby on Rails (snake case) and JavaScript (camel case). At
Neeto, this difference posed a major hurdle: the requirement for manual case
conversion between requests and responses. As a result, there was a significant
amount of repetitive code needed to handle this conversion.

Here’s a snippet illustrating the issue faced by our team:

```js
// For requests, we had to manually convert camelCase values to snake_case.
const createUser = ({ userName, fullName, dateOfBirth }) =>
  axios.post("/api/v1/users", {
    user_name: userName,
    full_name: fullName,
    date_of_birth: dateOfBirth,
  });

// For responses, we had to manually convert snake_case values to camelCase
const {
  user_name: userName,
  full_name: fullName,
  date_of_birth: dateOfBirth,
} = await axios.get("/api/v1/users/user-id-1");
```

This manual conversion process consumed valuable development time and introduced
the risk of errors or inconsistencies in data handling.

To streamline our workflow and enhance interoperability between the frontend and
backend, we decided to automate case conversion.

## Implementing automatic case conversion

Implementing automatic case conversion across Neeto products required a
thoughtful approach to minimize disruptions and ensure a smooth transition.
Here's how we achieved this goal while minimizing potential disruptions:

### 1. Axios Interceptors for Recursive Case Conversion

We created a pair of Axios interceptors to handle case conversion for requests
and responses. The interceptors were designed to recursively convert the cases,
managing the translation between snake case and camel case as data traveled
between the frontend and backend. This smooth transition simplified the
workflow, cutting out the requirement for manual case conversion in most
situations.

### 2. Custom Parameters to Control Case Conversion

To do a smooth rollout without breaking any products, and due to certain special
APIs requiring specific case conventions due to legacy reasons or external
dependencies, we introduced custom parameters `transformResponseCase` and
`transformRequestCase` within Axios. These parameters allowed developers to
opt-out of the automatic case conversion for specific API endpoints. By
configuring these parameters appropriately, we prevented unintentional case
conversions where needed, maintaining compatibility with APIs that required
different conventions.

This is how we crafted our axios interceptors:

```js
import {
  keysToCamelCase,
  serializeKeysToSnakeCase,
} from "@bigbinary/neeto-cist";

// To transform response data to camel case
const transformResponseKeysToCamelCase = response => {
  const { transformResponseCase = true } = response.config;

  if (response.data && transformResponseCase) {
    response.data = keysToCamelCase(response.data);
  }

  return response;
};

// To transform error response data to camel case
const transformErrorKeysToCamelCase = error => {
  const { transformResponseCase = true } = error.config ?? {};

  if (error.response?.data && transformResponseCase) {
    error.response.data = keysToCamelCase(error.response.data);
  }

  return error;
};

// To transform the request payload to snake_case
const transformDataToSnakeCase = request => {
  const { transformRequestCase = true } = request;

  if (!transformRequestCase) return request;

  request.data = serializeKeysToSnakeCase(request.data);
  request.params = serializeKeysToSnakeCase(request.params);

  return request;
};

// Adding interceptors
axios.interceptors.request.use(transformDataToSnakeCase);
axios.interceptors.response.use(
  transformResponseKeysToCamelCase,
  transformErrorKeysToCamelCase
);
```

Note that `keysToCamelCase`, `serializeKeysToSnakeCase` are methods from our
open source pure utils library
[`@bigbinary/neeto-cist`](https://github.com/bigbinary/neeto-cist).

While rolling out the change to all products, we wrote a JSCodeShift script to
automatically add these flags to every Axios API requests in all Neeto products
to ensure that nothing was broken due to it. Then the team had manually went
through the code base and removed those flags while making the necessary changes
to the code.

After the change was introduced the API code was much cleaner without the
boilerplate for case conversion.

```js
// Request
const createUser = ({ userName, fullName, dateOfBirth }) =>
  axios.post("/api/v1/users", { userName, fullName dateOfBirth })

// Response
const { userName, fullName, dateOfBirth } = await axios.get("/api/v1/users/user-id-1");
```

## Pain points

In our work towards automating case conversion within neeto, we encountered
several pain points.

### 1. Manual work is involved

During the rollout phase of our automated case conversion solution, there was an
unavoidable requirement for manual intervention. As we transitioned the existing
code bases to incorporate the new mechanisms for automatic case conversion
within Axios, each Axios call needed an adjustment to remove the manual case
conversion codes written before.

This stage demanded some manual work from our development teams. They updated
and modified existing Axios requests across multiple projects to ensure they
aligned with the new automated case conversion mechanism. While this manual
effort temporarily increased the workload, it was a necessary step to implement
the automated solution effectively across Neeto.

This phase highlighted the importance of a structured rollout plan and
meticulous attention to detail. Despite the initial manual workload, once the
changes were applied uniformly across the codebase, the benefits of automated
case conversion quickly became evident, significantly reducing ongoing manual
efforts and improving the overall efficiency of our development process.

### 2. Serialization Issues

As our initial implementation of automated case conversion, we used
`keysToSnakeCase` method, which recursively transforms all the keys to snake
case for a given object. It internally used `transformObjectDeep` function to
recursively traverse through each key-value pair inside an object for
transformation.

```js
import { camelToSnakeCase } from "@bigbinary/neeto-cist";

const transformObjectDeep = (object, keyValueTransformer) => {
  if (Array.isArray(object)) {
    return object.map(obj =>
      transformObjectDeep(obj, keyValueTransformer, objectPreProcessor)
    );
  } else if (object === null || typeof object !== "object") {
    return object;
  }

  return Object.fromEntries(
    Object.entries(object).map(([key, value]) =>
      keyValueTransformer(
        key,
        transformObjectDeep(value, keyValueTransformer, objectPreProcessor)
      )
    )
  );
};

export const keysToSnakeCase = object =>
  transformObjectDeep(object, (key, value) => [camelToSnakeCase(key), value]);
```

However, this recursive transformation approach led to a serialization issue,
especially with objects that required special treatment, such as `dayjs` objects
representing dates. The method treated these objects like any other JavaScript
object, causing unexpected transformations and resulting in invalid payload data
in some cases.

To mitigate these serialization issues and prevent interference with specific
object types, we enhanced the `transformObjectDeep` method to accommodate a
preprocessor function for objects before the transformation:

```js
const transformObjectDeep = (
  object,
  keyValueTransformer,
  objectPreProcessor = undefined
) => {
  if (objectPreProcessor && typeof objectPreProcessor === "function") {
    object = objectPreProcessor(object);
  }

  // Existing transformation logic
};
```

This modification allowed us to serialize objects before initiating the
transformation process. To facilitate this, we introduced a new method,
`serializeKeysToSnakeCase`, incorporating the object preprocessor. For specific
object types requiring special serialization, such as `dayjs` objects, we
leveraged the built-in `toJSON` method, allowing the object to transform itself
to its desired format, such as a date string:

```js
import { transformObjectDeep, camelToSnakeCase } from "@bigbinary/neeto-cist";
export const serializeKeysToSnakeCase = object =>
  transformObjectDeep(
    object,
    (key, value) => [camelToSnakeCase(key), value],
    object => (typeof object?.toJSON === "function" ? object.toJSON() : object)
  );
```

This resolved the serialization issue for the request payloads. Since the
response is always in JSON format, all values are objects, arrays, or
primitives. It won't contain such 'magical' objects. So we need this logic only
for request interceptors.

## Conclusion

In simplifying our web development workflow at Neeto, automating case conversion
proved crucial. Despite challenges during implementation, refining our methods
strengthened our system. By streamlining data translation and overcoming hurdles
like serialization issues, we've improved efficiency and compatibility across
our ecosystem.

If you're starting a new project, adopting automated case conversion mechanisms
similar to what we've built in Axios can offer significant advantages.
Implementing these standards from the beginning promotes consistency and
simplifies how data moves between your frontend and backend systems. Introducing
these practices early in your project's lifecycle help sidestep the difficulties
of adjusting existing code and establishing a unified convention throughout your
project's structure.

For existing projects, adopting automated case conversion might initially come
with a cost. Introducing these changes requires careful planning and execution
to minimize disruptions. The rollout process might necessitate manual updates
across various parts of the codebase, leading to increased workload and
potential short-term setbacks.

## Links

- [Human page](https://www.bigbinary.com/blog/axios-case-conversion)
