G.

Converting Laravel API requests and responses to camelCase

I love Laravel as a web framework. It’s got all the good things from Ruby on Rails, it’s opinionated just right and comes with a bunch of extremely useful packages that cover most needs in web application and web API development.

There is however one annoyance that I’ve been dealing with recently. By default, Laravel formats the JSON response keys in snake_case and assumes that’s how the request will come formatted as. The problem is that JavaScript has a convention of using camelCase fields so unless you want to deal with a mixed field formatting in your front-end code, you’ll need to do the conversion either front-end or back-end side.

Until recently I’ve been dealing with the issue on the front-end. For each API endpoint you need to create two types - an interface for the resource and a class for the actual model. Take a look at this example:

interface UserResource {
  email: string;
  first_name: string;
  last_name: string;
}

class User {
  public email: string;
  public firstName: string;
  public lastName: string;

  constructor({ email, first_name, last_name }: UserResource) {
    this.email = email;
    this.firstName = first_name;
    this.lastName = last_name;
  }}
}

From that point forward, you only deal with the User class. Here’s how I’d deal with the HTTP request in Angular:

class UserService {
  constructor(private readonly http: HttpClient) {}

  public getUsers(): Observable<User[]> {
    return this.http
      .get<UserResource[]>('/api/users')
      .pipe(
        map(users => users.map(user => new User(user)))
      );
  }

It’s been working quite well however there are two major drawbacks:

  • data serialisation is a pain - if you’re storing your models in some state management system (like NGXS or NgRx), you really should work with plain interfaces
  • the model class does the mapping and has knowledge of the API response structure, which could be treated as a violation of separation of concerns

There is a better and more elegant solution. As long as you have control of the back-end endpoints of course.

Instead of doing the mapping front-end side, you can inject middleware into every request and response handled by Laravel. To do that, you’ll need to create two middleware classes:

php artisan make:middleware ConvertResponseFieldsToCamelCase
php artisan make:middleware ConvertRequestFieldsToCamelCase

And add them to the api middleware group in your HTTP Kernel class. Here’s the sample code:

// ConvertResponseFieldsToCamelCase.php
class ConvertResponseFieldsToCamelCase
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $response = $next($request);
        $content = $response->getContent();

        try {
            $json = json_decode($content, true);
            $replaced = [];
            foreach ($json as $key => $value) {
                $replaced[Str::camel($key)] = $value;
            }
            $response->setContent($replaced);
        } catch (\Exception $e) {
          // you can log an error here if you want
        }

        return $response;
    }
}

// ConvertRequestFieldsToCamelCase.php
class ConvertRequestFieldsToCamelCase
{
    /**
     * Handle an incoming request.
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \Closure  $next
     * @return mixed
     */
    public function handle($request, Closure $next)
    {
        $replaced = [];
        foreach ($request->all() as $key => $value) {
            $replaced[Str::snake($key)] = $value;
        }
        $request->replace($replaced);

        return $next($request);
    }
}

That’s it. All the request fields made to the API will now be converted to snake case automatically, as will the responses. If you’re using Laravel’s concept of resources, remember to still format the keys in snake_case. The middleware will convert them to camelCase and if at any point you decide to change the API to snake case response format again, you can just disable the middleware.

I have just one additional warning if you’ve missed it in the code example above. It only replaces the top-level keys and doesn’t traverse the associative arrays. So if the request looks like this:

{
  "userPreferences": {
    "defaultLocale": "en"
  }
}

Then in your Laravel controller $request->all() will contain:

[
  'user_preferences' => [
    'defaultLocale' => 'en'
  ]
]

This is a behaviour I want (because I’d like to keep the JSON fields in the database formatted in camelCase) but your requirements may be different and you may need to update the middleware to handle that situation.

Share