Hi there, welcome to the third part of Building API with Rails series. In this part we are going to serialize our API response.
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
- Jbuilder
- Active Model Serializers (AMS)
- JSONAPI-RB
- jsonapi-serizlizer
- 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.
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.
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 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.
Please feel free to give your feedback on the comment section below or ping me at or . Have a great time