Using Fast JSON API

Steve Carter
4 min readOct 11, 2021

The Fast JSON API is a JSON serializer for Rails APIs. It provides a way for us to generate serializer classes for each resource object in an API that is involved in customized JSON rendering. We can use these serializer classes to define the specific attributes we want objects to share or not share, along with things like related object attributes.

The result is that in a controller action, rather than writing a custom render each time, we write out a serializer for each object once and use Fast JSON API to control the way our data is structured. With Fast JSON API, we can extract and separate work into Serializer classes, keeping our controller cleaner.

Setting up Fast JSON API

To include Fast JSON API, add gem 'fast_jsonapi' to your Rails project's Gemfile and run bundle install.

Once installed, you will gain access to a new generator, serializer.

Implementing the Fast JSON API

With the new serializer generator, we can create serializer classes for our models, which will be available to us in any controller actions later. Running rails g serializer bird , will create a serializers folder within /app, and inside, bird_serializer.rb. With the serializer, we can start to define information about the model and or related models we want to share in our API.

Updating the Controller Action

our render json: BirdSerializer.new(bird) the statement can be used on all BirdController actions we want to serialize, so if we were to add an index, for instance, we just pass in the array of all models as well:

def index
birds = Bird.all
render json: BirdSerializer.new(birds)
end

Adding Attributes

When rendering JSON directly, controllers will render all attributes available by default. These serializers work the other way around — we must always specify what attributes we want to include. In our example, birds have name and species attributes and locations have latitude and longitude attributes, so to include these we would update both serializers. For sightings, we could include the created_at attribute:

class BirdSerializer
include FastJsonapi::ObjectSerializer
attributes :name, :species
end
class LocationSerializer
include FastJsonapi::ObjectSerializer
attributes :latitude, :longitude
end
class SightingSerializer
include FastJsonapi::ObjectSerializer
attributes :created_at
end

this is how our API should look:

{
"id": "2",
"type": "sighting",
"attributes": {
"created_at": "2019-05-14T16:39:37.011Z"
}
}

We can also use attributes to access related objects, adding them alongside normal object attributes:

class SightingSerializer
include FastJsonapi::ObjectSerializer
attributes :created_at, :bird, :location
end

This results in our rendered JSON including an "attributes" object with "created_at", "bird", and "location":

{
"id": "2",
"type": "sighting",
"attributes": {
"created_at": "2019-05-14T16:39:37.011Z",
"bird": {
"id": 2,
"name": "Grackle",
"species": "Quiscalus Quiscula",
"created_at": "2019-05-14T16:39:36.917Z",
"updated_at": "2019-05-14T16:39:36.917Z"
},
"location": {
"id": 2,
"latitude": 30.26715,
"longitude": -97.74306,
"created_at": "2019-05-14T16:39:36.942Z",
"updated_at": "2019-05-14T16:39:36.942Z"
}
}
}

However, here, we have no control over what attributes are included in the related objects, and so we get all the attributes of "bird" and "location".

Adding Relationships

Object relationships can be included in serializers in two steps. The first step is that we include the relationships we want to reflect in our serializers. We can do this in the same way that we include them in the models themselves. A sighting, for instance, belongs to a bird and a location, so we can update the serializer to reflect this:

class SightingSerializer
include FastJsonapi::ObjectSerializer
attributes :created_at
belongs_to :bird
belongs_to :location
end

However, when visiting http://localhost:3000/sightings/2, Fast JSON API will display a new "relationships" object, but will only provide limited information, including the id of the related object:

{
"id": "2",
"type": "sighting",
"attributes": {
"created_at": "2019-05-14T16:39:37.011Z"
},
"relationships": {
"bird": {
"data": {
"id": "2",
"type": "bird"
}
},
"location": {
"data": {
"id": "2",
"type": "location"
}
}
}
}

Setting these relationships up is necessary for the second step. Now that we have included relationships connecting the SightingSerializer to :bird and :location, to include attributes from those objects, the recommended method is to pass in a second options parameter to the serializer indicating that we want to include those objects:

def show
sighting = Sighting.find_by(id: params[:id])
options = {
include: [:bird, :location]
}
render json: SightingSerializer.new(sighting, options)
end

The result:

{
"data": {
"id": "2",
"type": "sighting",
"attributes": {
"created_at": "2019-05-14T16:39:37.011Z"
},
"relationships": {
"bird": {
"data": {
"id": "2",
"type": "bird"
}
},
"location": {
"data": {
"id": "2",
"type": "location"
}
}
}
},
"included": [{
"id": "2",
"type": "bird",
"attributes": {
"name": "Grackle",
"species": "Quiscalus Quiscula"
}
},
{
"id": "2",
"type": "location",
"attributes": {
"latitude": 30.26715,
"longitude": -97.74306
}
}
]
}

Because we have a BirdSerializer and a LocationSerializer, when including :bird and :location, Fast JSON API will automatically serialize their attributes as well.

Conclusion

There is a lot more you can do with the Fast JSON API gem, and it is worth reading through their documentation This is an external link. to become more familiar with it. It is possible, for instance, to create entirely custom attributes!

What we covered is enough to get us close to where we were creating our own customized serializers. We do not get to choose exactly how data gets serialized the way we do when writing our own serializer classes, but we gain a lot of flexibility by using the Fast JSON API.

The Fast JSON API gem provides a quick way to generate and customize JSON serializers with minimal configuration. Its conventions also allow it to work well even when dealing with a large number of related objects.

Overall, the goal is to get you comfortable enough to get Rails APIs up and running. With practice, it is possible to build a multi-resource API, complete with many serialized JSON rendering endpoints within minutes.

Being able to quickly spin up an API to practice your fetch() skills is an excellent way to get familiar with asynchronous requests. As you move towards building larger frontend projects, you'll also quickly need a place to persist data and handle things like login security.

--

--