RESTful JSON API With Rails 5
The Rails documentation says that Ruby on Rails is a web-application framework that includes everything needed to create database-backed web applications according to the Model-View-Controller (MVC) pattern. However, several companies were using rails just to create a backend API, and that was translated into having tons of unnecessary code dependencies. That’s why since version 5, Rails started offering a way to create an API-only applications, which only provides the required things to have a super powerful API.
Find a more detailed explanation in the API-only documentation.
This post is a self-guide to create a new API-only project. It’s not a tutorial that resolves and exercise, but a guide to remember the steps to follow in the creation of an API from scratch.
Creating an API-only rails project
To start creating an API-only Rails applications, ensure that your Rails version is equal or higher than 5.
> rails -v
Rails 5.2.0
Then create a new API-only application:
> rails new my-api --api -T
The --api
part, tells Rails that we want an API-only application. The -T
part is necessary if you want to avoid the use Minitest as the default testing framework. I normally use rspec
, so I would include it.
Arriving here, you have a Rails API-only application with these properties:
-
It still provides things out of the box. It provices a set of middleware with all the stuff we could need for an API.
-
The
ApplicationController
inherit fromActionController::API
instead of fromActionController::Base
. -
As any other Rails application, it will provide
routing
resources and helpers,caching
,authentication
, etc.
Project Setup
This would depend on personal preferences, but I normally setup every new project with this gems:
- rspec-rails - I have nothing against Minitest, but I normally use Rspec testing framework. It is needed to run
rails generate rspec:install
to install it in a project. - shoulda_matchers - Include more matchers to Rspec
- factory_bot_rails - Very userful tool to use fixtures.
- faker - A library for generating fake data such as names, addresses, and phone numbers.
- database_cleaner - We can ensure a clean environment before the execution of each test. It needs to include a piece of code in our
rails_helper.rb
file.
Find here a commit adding all these dependencies.
API Endpoints
Now that we have our project to start, it’s the best moment o start thinking in the endpoints we will need. It will help us to have a clear idea of the models and the controllers that we will need.
Write down a draft of the endpoints.
Model definition
We have a list of endpoints, so we know more or less the main models and its associations.
- Create the database: models, associations, migrations
- TDD: add tests to check associations and presence validations
- Fix the tests adding the validations and associations to the models.
- Create factories for each model using
Faker
value. They will be useful in the next step.
At this point we have valid models and its associations and validations.
Controllers and routes
- Generate the required controllers with
rails g controller Name
. - Create
requests
tests. You can use this one as a guide. - The errors in the tests will guide you to add the required routes.
Responses
I like controller responses with this structure:
render json: object, status: status
Look at this example.
Exception handler
In the case that a searched record does not exist, Rails will through an error. Instead or raising the error, it would be necessary to rescue that error and notify the user with the problem.
We should rescue any kind of error on which we need to notify the API client. To do this, we can create a concern:
# app/controllers/concerns/exception_handler.rb
module ExceptionHandler
# provides the more graceful `included` method
extend ActiveSupport::Concern
included do
rescue_from ActiveRecord::RecordNotFound do |e|
render json: { message: e.message }, status: :not_found
end
rescue_from ActiveRecord::RecordInvalid do |e|
render json: { message: e.message }, status: :unprocessable_entity)
end
end
end
And include it in the ApplicationController
:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include ExceptionHandler
end
Versioning
It is not necessary to add versioning in your first version. But when the day comes in which you have to add a second version, you can afford it in different ways.
Have in mind that in any kind of solution you would have to
- Add a route constraint to select the version
- Use different namespaces for the controllers, depending on the version.
There are different options to apply versioning in our project. The most common are:
- Using different
routes
andnamespaces
. - Selecting the version from the headers (using a route constraint). Follow this documentation.
Serializers
I have always used Active Models Serializers, but I recently discovered Fast JSON API from Netflix, and sounds promising. I will use that last one in my next project.
https://github.com/Netflix/fast_jsonapi
Pagination
Any serious production API have to include pagination. Will_paginate or kaminari will do the trick.
Authentication and authorization
Follow this guide.
For authorization, the gem Pundit is great.
Caching
Conditional GETs: Rails handles conditional GET (ETag and Last-Modified) processing request headers and returning the correct response headers and status code. All you need to do is use the stale?
check in your controller, and Rails will handle all of the HTTP details for you.