How we build JSON APIs with Rails

Nenad Nikolić
  —  4 min read

A couple of months ago, we started a new project and decided to use Ember on the front and Rails as an API backend.

We made a decision to make an API according to the JSON API specification since Ember has great support for it, so it was a no-brainer to use the Active Model Serializer gem which also has great support for the JSON API specification.

JSON API's with Rails

The problem

Early in development, we found ourselves repeating similar code in multiple places in our controllers.

  def create
    user = User.new(user_params)

    if user.save
      render json: user, serializer: UserSerializer, status: 201
    else
      render error: user.errors, serializer: ErrorSerializer, status: 422
    end
  end

Not just the create method, but generally, our controller started to have a lot of boilerplate code. Another problem was handling status codes. JSON API standard has strict policies on status codes for each response type, i.e create(201), update(200), error(422) etc.

Handling status codes manually and having a lot of boilerplate code makes the entire process error-prone, and naturally, we wanted to avoid that.

Solution

Two gems could potentially make our lives easier.

The first one was RocketPants which we used in our previous projects and we liked it. However, RocketPants isn't compatible with the new version of ActiveModelSerializers and the last commit on RocketPants was two years ago so the project looked abandoned.

The second gem that could potentially handle our problems was Grape. It's great but it would be a full replacement for Rails, and we didn't want to make a tradeoff just like that.

Ultimately, we decided to make our custom solution and we named it JsonApiResponders. We always liked the syntax of the very popular responders gem which was part of Rails until v4.2. and since we wanted to start as simple as possible we created our own custom respond_with method.

At the beginning, the method only handled status codes based on the action name. In our controllers, we only use the default CRUD action names such as index, show, create, update and destroy, so we could easily determine which status code to return.

Any other action would lead to the creation of a dedicated controller (which itself only has default CRUD actions). For quite some time we have been managing our controllers like this and we were happy when we found out that DHH does it the same way.

We moved our logic of determining whether the object is valid and which HTTP status code to return into our respond_with method, so our controller actions started to be easy one-liners:

  def create
    respond_with User.create(user_params), serializer: UserSerializer
  end

Now our controllers started to look much cleaner and more readable and we wanted to bring it a step further - which brings us to error handling.

Error Handling

One of the built-in features of JsonApiResponders is the ability to handle rescuing exceptions. At this point, it will rescue ActiveRecord::RecordNotFound and ActionController::ParameterMissing errors which we believe are the most occurring ones.

  def show
    respond_with User.find(params[:id]), serializer: UserSerializer
  end

If the given user id does not exist, JsonApiResponders will catch the error and return a JSON API compliant response with the appropriate error message.

ActionController::ParameterMissing will also be handled automatically in the following way.

  def create
    respond_with User.new(user_params), serializer: UserSerializer
  end

If there is no user param sent in the request, ActionController will raise an error here, which JsonApiResponders will handle automatically.

JsonApiResponders also supports responding with custom errors. You can use the respond_with_error method to handle any kind of errors. This method will render an error message as described in the JSON API specification. Below you can see an example of how it can be used with the Pundit gem:

rescue_from Pundit::NotAuthorizedError do
  respond_with_error(403, 'Not Authorized')
end

Compatiblity

JsonApiResponders is compatible with both ActiveModelSerializers and JsonapiRails gems. If you are using ActiveModelSerializers you don't need to configure anything, it will work out of the box. However, if you are using JsonapiRails gem you need to modify render_method option in JsonApiResponders configuration. The configuration will most likely be located in config/initializers folder.

JsonApiResponders.configure do |config|
  config.render_method = :jsonapi
end

After you make this change, you are good to go!

Conclusion

We've been using our JsonApiResponders on multiple projects and are really satisfied with the results. We don't have to handle and think about response codes or whether we’re compliant with the JSON API standard. Everything is handled automatically! We don't have if clauses on create and update methods to check for errors, and there is also simple error handling for record not found and missing parameters.

All of this makes our controllers much cleaner and readable. In most cases, the response will be a one-liner. All in the comfort of Rails Controllers.

48346133-B7BD-45CD-9E24-6CC0D844E9FF
Greetings from our lovely team!
1/4
Achievement unlocked
Resize Master