Implementing Serializers In Our Endpoints

Creating active model serializers in our Rails endpoints

A Quick Recap

In the last article, we covered how JWT (JSON Web Token) works. In order to implement JWT into our endpoints, we need to add it in as meta data for our responses. To achieve that, we will need to implement serializers for our Rails API.

Feel free to refer to the part 8 branch of the GitHub repository if needed.

This is the 8th installment of my Developing A Cross-Platform iOS & Android Social Media App series.

What Are Serializers?

Before we setup serializers, we should take a look at what a serializer does and why we need it to implement JWT. Serializers formats our data prior to outputting the response. It keeps our response clean, and allows us to add meta data for access tokens (JWT). Here is our current successful /login response:

{
   "updated_at" : "2020-02-10T18:09:33.148Z",
   "created_at" : "2020-02-10T18:09:33.148Z",
   "email" : "[email protected]",
   "id" : "50cc4c1f-aa26-4a99-a1e3-e345f5ba58ee"
}

If we implement a user serializer, we can have it automatically format that response to this:

{
   "user" : {
      "id" : "50cc4c1f-aa26-4a99-a1e3-e345f5ba58ee",
      "email" : "[email protected]"
   },
   "meta" : {
      "access_token" : "123"
   }
}

Our user data is now under a user attribute. Notice how we no longer have updated_at or created_at. In some scenarios, we never use that information so there is no need to output it and make the response larger.

We also have the meta attribute which allows us to append the access_token to our response. The frontend can then extract that access_token in a clean way. Here is an example of how you could extract data from the response above using javascript:

const {user, meta} = response;
const {access_token} = meta;

Serializers are much more powerful than selecting which attributes to output. We can also output relations, such as the first 3 comments for a post. Using serializers keeps our responses small and allows us to combine multiple responses into one. For example, not having to hit /comments endpoint to get the first 3 comments for each post in the /posts endpoint.

Setting Up Serializers

We would need to first setup serializers. Add gem 'active_model_serializers' to your Gemfile and run $ bundle install:

...
gem 'jwt'
gem 'active_model_serializers'
...

We will now need to create an initializer that will configure active_model_serializers every time the server is ran. Initializers are located under config/initializers. Lets create one called active_model_serializers.rb. You can create the file via terminal by using the touch command: $ touch config/initializers/active_model_serializers.rb. Add the following lines to your new initializer:

ActiveModel::Serializer.config.adapter = :json 
ActiveModelSerializers.config.key_transform = :underscore

Lets break this down line by line, starting with:

ActiveModel::Serializer.config.adapter = :json

ActiveModel::Serializer: This is the class that active_model_serializer gem provides.

config: This is the config class that we can use to set our configurations.

adapter: This is a setting to determine what our adapter is. Adapters choose how our responses should be serialized. By default it uses the attributes serializer.

:json: This is the adapter we will use that formats our response as JSON.

ActiveModelSerializers.config.key_transform = :underscore

key_transform: This setting allows us to configure our json keys to be CamelCase, lowerCamelCase, underscore_case (snake_case), dashed-case, or unaltered.

:underscore: we will be using underscore for our keys. This is becuase the Ruby language uses underscores and it would make it easier if data being sent, manipulated, and recieved had consistent key names. Even though our frontend is in Javascript, which uses camelCase, we won't be doing any manipulation on our frontend. Sorting, searching, and etc will all be done in the backend Rails API. I do this to remove any complexity out of the app and make updates easier when it comes to anything data related.

Creating Our First Serializers

Create a serializers directory:

$ mkdir app/serializers

Create a v1 directory:

$ mkdir app/serializers/v1

Create a user_serializer.rb file under app/serializers/v1/:

$ touch app/serializers/v1/user_serializer.rb

Add the following to the file:

class V1::UserSerializer < ActiveModel::Serializer
  attributes (
    :id,
    :email
  )
end 

V1::UserSerializer: Similar to controllers, this declares a class named UserSerializer under v1. This tells us that this serializer will be used by controllers under v1.

ActiveModel::Serializer: The class will inherit the ActiveModel::Serializer class.

attributes: These are the attributes that will be used in the serialization.

:id, :email: These would be the only two columns we care about right now. We won't need created_at or updated_at for this endpoint.

Now that our serializer is setup, we can test it out by calling the login endpoint. The controller will automatically look for the UserSerializer under v1. There is a way to manually set the serializer that you want to use, for example a different user serializer, but we will cover that in the future.

Testing The Serializer

We can now test the serializer. Call the login endpoint:

$ curl -H "Content-Type: application/json" -X POST -d '{"user":{"email": "[email protected]","password":"123456"}}' http://localhost:3000/v1/users/login | json_pp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   125    0    74  100    51    241    166 --:--:-- --:--:-- --:--:--   242
{
   "user" : {
      "id" : "7545d290-962f-45bb-891d-f7e43b4fbf68",
      "email" : "[email protected]"
   }
}

Sweet! Our API now only returns the attributes that were specified in our serializer.

Adding Meta Data

Serializers give us the option to add meta data. This would be the extra data appended to our response. In this case we want to return our JWT access token. Since we have not worked on generating a token in the controller yet, we will just hardcode a string.

Take a look at the render method of your login endpoint in your app/controllers/v1/user_controller.rb file:

return render json: user,
      status: 200

We have the option of passing a hash as a value for the meta key. Create a hash with a mock access_token and set it to the meta key:

return render json: user,
      meta: {access_token: '123'},
      status: 200

Now we can test the login endpoint again:

$ curl -H "Content-Type: application/json" -X POST -d '{"user":{"email": "[email protected]","password":"123456"}}' http://localhost:3000/v1/users/login | json_pp
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100   155    0   104  100    51    735    360 --:--:-- --:--:-- --:--:--   737
{
   "user" : {
      "id" : "7545d290-962f-45bb-891d-f7e43b4fbf68",
      "email" : "[email protected]"
   },
   "meta" : {
      "access_token" : "123"
   }
}

Our meta deta is now stored under the meta key in our JSON response. We are now ready to create our JWT module in the next section.