Building API in Rails - Part 3

Hi there, welcome to the third part of Building API with Rails series. In this part we are going to serialize our API response. ruby on rails api serializer

Serialization is the process of translating a data structure or object state into a format that can be stored (for example, in a file or memory data buffer) or transmitted (for example, over a computer network) and reconstructed later. [Source: Wikipedia]

Since we are building API’s we will mostly deal with how the ruby objects can be serialized into a string in JSON format and transmitted.

In Ruby on Rails there are different available gems that we can use as a JSON serializer such as

  1. Jbuilder
  2. Active Model Serializers (AMS)
  3. JSONAPI-RB
  4. jsonapi-serizlizer
  5. and others…

I will be using the jsonapi-serializer which is forked from Fast JSON API. I am using this particular serializer because its fast and follows the JSON:API specifications.

But why use an external serializer gem anyway?

In the previous part, we have seen that Rails has its own render json: User.all that renders the response in JSON format. And to modify the API response we can use Rails own as json method so what’s the point of using an external serializer gem?

In short, if we have to modify the API response into a complex format such as JSON:API specification then doing it manually will increase complexity, our codebase will look dirty and hard to maintain.

Integrating jsonapi-serializer (FAST JSON API)

The first thing we need to do is add the required jsonapi-serializer gem in our Gemfile and run bundle install.

gem 'jsonapi-serializer'

Then let’s go ahead and used the rails generator to generate the serializer for our User.

rails g serializer User fullname gender email

This will create a new serializer in app/serializers/user_serializer.rb

class UserSerializer
  include JSONAPI::Serializer
  attributes :fullname, :gender, :email
end

And in our users controller let’s change the def show api as

def show
    @user = User.find(params[:id])
    render json: UserSerializer.new(@user)
end

Now we can see the API response format using curl in our terminal.

 curl --location --request POST 'http://localhost:3000/api/v1/users' --header "Content-Type: application/json" \
--data-raw '{"user": {"fullname": "Sajan Basnet", "email": "sajan@gmail.com", "password": "something", "gender": "1"}}'

and then

curl --location --request GET 'http://localhost:3000/api/v1/users/1'

We will get the response as this

{
   "data":{
      "id":"3",
      "type":"user",
      "attributes":{
         "fullname":"Sajan Basnet",
         "gender":1,
         "email":"sajan@gmail.com"
      }
   }
}

If we go and run the test for controllers some test cases will fail as the response now comes in JSON:API specification so we need to modify our test cases which will be something like this.

require 'rails_helper'

RSpec.describe 'Api::V1::Users', type: :request do
  describe 'GET api/v1/users' do
    it 'returns list of available users' do
      create(:user)
      total_users = User.all.count
      get api_v1_users_path
      expect(response).to have_http_status(200)
      resp = JSON.parse(response.body)
      expect(resp['data'].count).to eq(total_users)
    end
  end

  describe 'GET api/v1/users/:id' do
    it 'returns a user' do
      user = create(:user)
      expected_response = { 'email' => user.email }
      get api_v1_user_path(id: user.id)
      expect(response).to have_http_status(200)
      resp = JSON.parse(response.body)
      expect(resp['data']['attributes']).to include(expected_response)
    end
  end
end

Note: I have only put here the updated test cases that had failed.

Another thing I also like to do is install another gem known as jsonapi.rb which works pretty well with the jsonapi-serializer. Let’s add it and do bundle install.

gem 'jsonapi.rb'

Create an initializers as jsonapi.rb inside config/initializers/jsonapi.rb and add this

require 'jsonapi'

JSONAPI::Rails.install!

Finally, in the users controller let’s change all the existing API’s and we will have something like this

# frozen_string_literal: true

module Api
  module V1
    class UsersController < BaseController
      def index
        render jsonapi: User.all
      end

      def show
        @user = User.find(params[:id])
        render jsonapi: @user
      end

      def create
        user = User.new(user_params)
        if user.save
          render jsonapi: user, status: :created, code: '201'
        else
          render jsonapi_errors: user.errors, 
                 status: :unprocessable_entity, code: '422'
        end
      end

      def update
        user = User.find(params[:id])
        if user.update(user_params)
          render jsonapi: user, status: :ok, code: '200'
        else
          render jsonapi_errors: user.errors, 
                 status: :unprocessable_entity, code: '422'
        end
      end

      def destroy
        user = User.find(params[:id])
        if user.destroy
          render jsonapi: user, status: :ok, code: '200'
        else
          render jsonapi_errors: user.errors, 
                 status: :unprocessable_entity, code: '422'
        end
      end

      private

      def user_params
        params.require(:user).permit(:fullname, :email, :password, :gender)
      end
    end
  end
end

All this process may not seem useful right now since we haven’t added any associations and also we right now don’t have to fetch any complex API response. Once the application grows big this process will certainly be very useful.

Great :tada: :tada: we have come to the end of this chapter. I hope it helped whoever is reading this.

Thanks for reading till the end we have come to the end of part 3 of the Building API with Rails series. In the upcoming parts, we will be securing our API with JWT, adding SWAGGER for API documentation, and others.

The code base is available here. :beers:

Please feel free to give your feedback on the comment section below or ping me at or . Have a great time :smiley_cat:

Building API in Rails - Part 3