Dealing with JSON in Rails is pretty easy and straight forward. The support is built into the framework, request data is automatically available in params hash. Then we just have to say
render :json and we are done.
If JSON is one of the formats which we use. For instance, we can render both JSON and HTML in the same controller action. Or we may create an action intended for AJAX call which changes some model and returns JSON with status field.
On the other hand, we may use Ruby on Rails for building JSON API – app with the assumption that JSON is the only format we support. In such a case we know that the app won’t render HTML. There is no View layer (or you can say that JSON is your View layer). We probably won’t need cookies support and the code specific to handling browser requests.
Ruby on Rails has modular structure – you can opt out of the things which you don’t want to use. But if we are talking about API only Rails app already did it for us and published in the gem called rails-api.
What I care is “Don’t require things which you won’t use”. There might be bugs hiding there or security vulnerabilities connected with it. And of course there are other reasons:
More Lightweight Application
I generated two fresh applications – one using
rails-api new and the second using
rails new. The environment was consistent – Ruby 2.1.0, Rails 4.0.2. Then I started the server with rails s and measured RAM memory taken by server process. The results are following:
This is 15% less. Obviously when you start adding new gems the relative difference will be smaller.
The same benchmarking environment as above. I created controller action which loads 100 User records from the database and renders them as JSON. I placed exactly the same code in rails-api application and regular rails application. Then I measured the server response time for a bunch of requests and took the avarage value. It looks like:
This is 12% faster.
The controller scaffold which comes with rails-api is really cool. It disables new and edit actions (they don’t make sense because we do not display forms) and provides sensible default code inside other actions.
Easy Installation and No Maintenance Costs
Last but not least, rails-api is super easy to install and learn. All you have to do to bootstrap new app is:
It’s worth to spend 10 minutes of your time for learning it. Check out the docs.
Active Model Serializers
If you want to build JSON API it’s good to have control over the generated JSON. And here we need the second gem is active_model_serializers.
Let’s look at a sample serializer:
1 2 3 4 5 6
As you can see UserSerializer is used to serialize User model. Using attributes we define what should be included in the generated JSON. You may think that this is repeating yourself (the fields are already defined in database schema), but in my opinion you rarely render all the fields, more often you want to hide some internals.
As you can see we embed associated records using familiar syntax: has_one and has_many. Serializing Address and Package will be handled using AddressSerializer and PackageSerializer, respectively.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Serializers are just classes which inherit from ActiveModel::Serializer. You can define regular methods and the model being serialized is accessed by object method.
How does the code inside UsersController look like?
1 2 3 4 5 6 7 8 9
Pretty simple, You just say: “I want JSON” and you have JSON rendered with proper serializer.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
More about Associations
Let’s imagine following use case, we want to get information about a package given its id. And we want it to contain information about the owner (user).
1 2 3 4 5
What’s different here from model code? Package
belongs_to user, but here it says
has_one user. From the point of view of serializers
belongs_to is exactly the same as
has_one, hence we have only has_one and has_many.
Now let’s go back to
UsersController#index after our changes. We hit the action again and what do we get this time?
1 2 3
That’s an infinite loop: user contains package, package contains user, user contains package… How can we solve this?
1 2 3 4 5 6 7
1 2 3 4 5 6 7 8 9 10
We can use
:embed option and include only ids of the packages. This has a drawback: if a client of our API wants not only id of the package, but also its status then he will have to make a separate request for each package. Certainly this is a situation which we want to avoid.
1 2 3 4 5 6 7
1 2 3
:serializer option to specify different serializer than that inferred from naming conventions. We create
ShortPackageSerializer, which doesn’t contain user embeded. What’s more you can put
PackageSerializer in the inheritance hierarchy so you DRY.
In my opinion this solution is pretty clean. We have separate class for each representation and we are able to share some pieces of code by using inheritance. Of course, this may become too complex if the inheritance hierarchy grows very big. However if we limit ourselves to 2-3 serializers per model the code should stay clear and maintainable.
Use rails-api if you are building API-only application. It’s easy to learn and maintenance won’t hit you later, because you can opt out without any problems.
Use active_model_serializers for better control over the generated JSON.