The json:api spec isn’t just limited to how the server should format JSON responses. It also speaks to how the client should format requests, how to handle sorting, pagination, errors, and how new resources should be created. It even speaks to which media types and HTTP codes should be used.

We’re going to be implementing many of these things in our Rails app, and we’ll also look at how we might test them. Lastly, we’ll take a quick look at one possible authentication strategy and some tools we can use to produce beautiful documentation for our API.

json:api Is Big

The json:api spec is pretty big! It covers many of the possible features you may want to implement in a JSON API. May is the key word… you don’t have to implement all of them for your API to be json:api compliant. For example: You don’t have to have to implement sorting, but if you do, the json:api spec will tell you how it must be done.

Media types

One of the first things you’ll notice with json:api is that it doesn’t actually use the application/json media type. The reason for this is that the group behind json:api actually went through the proper process and channels to register their own media type, which is application/vnd.api+json. Part of the contract of the server is that it will respond with the Content-Type header set properly to this media type.

The easiest way I was able to find to get this working without having to set it manually in every action is to create an initializer to set them. I called it register_json_mime_types.rb, and it contains:

Now whenever a request is made it will automatically have Content-Type:application/vnd.api+json; charset=utf-8 set in the response headers.

Creating a Resource

One of the questions that came up from my last article was how we should go about creating a resource. What format should the data come in? What URL should the data be sent to? How should the server respond on success? On errors? Thankfully all of those answers are available, and my goal is to show how to create a resource while following the json:api spec.

Their fields don’t matter too much, and they have the relationship of a RentalUnit belonging to a User and a User having many RentalUnits.

Routing

Routing is probably one of the easiest parts. The specification follows RESTful routing pretty much identically to what Rails gives you by default when you define the route with resources :rental_units.

The specific route we are interested in for creating a resource is POST /rental_units HTTP/1.1.

Format of data posted

If you’ve done very much Rails development before, you’re probably used to data being posted to the server in a fairly simple format, which may look somewhat like this:

{
rental_unit: {
price_cents: 100000,
rooms: 2,
bathrooms: 1
}
}

We’ll have to change this a little bit to conform to the specification. In the guide we see that it must be formatted like this:

This matches how json:api is formatted on response, so it is at least familiar/consistent!

To ensure that our API responds correctly, let’s write a test. The test will post the data to the correct URL, and will then verify that the server responded with a 201 HTTP status code (which means resource created). After that, we’ll look for a Location header, which tells us where we can find this new resource that was created.

Lastly, we’ll look to verify that it responded in the correct json:api response format using a custom matcher which I’ll include below.

So if this is what our test looks like, what might the controller look like? It ends up looking fairly similar to how it might have before. The only difference here is that I have to dig a little deeper to get to the attributes coming in through the params object.

When Errors Occur

You might have noticed above that I have a special method called respond_with_errors for when the @rental_unit object is unable to save. But before we get to that, let’s take a look at how json:api expects us to format the errors:

I’ve written a small test to make sure that the API responds correctly. It again uses a custom rspec matcher which I’ll include below. What I am looking for here is that it responds with the 422 HTTP code (unprocessable entity) and that it contains an error for a specific field.

You may be thinking at this point why I mentioned class. Doesn’t this normally happen within the index action of the RentalUnitsController? Normally yes, but while doing this, I found that it was a bit more code than I was comfortable with to leave it all inside the controller. It’s also a good example of how you might extract some complicated logic out of the controller into its own class or module.

The RentalUnitsIndex class has one job, to handle preparing the queries and data necessary to respond to the the GET /rental_units HTTP/1.1 request. It receives self (the controller) so that it can access things such as the params object as well as the URL helpers.

The nice thing about the way this is written is that it would be quite easy to test what might end up being complicated logic to perform sorting, pagination, and maybe at some point in the future, filtering of the rental units.

If this were an API I was developing for real, I would most likely extract a lot of these generic methods into a parent class.

Sorting with json:api

Sorting in json:api is done through a single query param called sort which comes through the URL. It might look like this ?sort=-rooms,price_cents, which would sort descending by the rooms field, and then ascending by the price_cents field.

This functionality is handled by the sort_params method, which farms out the work to a module called SortParams. This module has the job of taking a string such as -rooms,price_cents and converting it into the usual Hash that the order method wants to receive. From -rooms,price_cents to {rooms: :desc, price_cents: :asc}.

Pagination with json:api

With pagination, most of the details are left up to the programmer to decide. That is because pagination could be done quite differently from app to app, either page by page or by where your cursor is on the screen (say in an infinite scroll view). What is dictated by the spec is that you should pass this information through query params in the page key.

It may end up looking like this: ?page[number]=1. You are also required to include a links key in the response which provides links to the current page (self), the first, previous, next, and last links. Most of these details are handled by the RentalUnitsIndex class in combination with the will_paginate gem.

These are included in the rendered response from within the controller which calls out to our helper class:

One interesting but slightly unrelated thing I discovered while working on this article was that params has changed in Rails 5. It is now a different object instead of the old HashWithIndifferentAccess object. Thanks to Eileen Uchitelle for a great article pointing out what has changed.

Authentication

The type of authentication you need depends on the type of application you are building. If it is a public API, you might not need it at all, but more and more I have seen a simple API key given out, allowing the server to track your usage and giving them more control over its access.

Alternatively, it may be an API which powers a single page application or a mobile app. Here you will still go with a token solution, perhaps like the one that devise_token_auth gives you, but it is a little bit more complicated to implement because there is more security involved (the token may change on every single request).

The main thing to take away is that there are no cookies involved… there is no magical state that the browser handles for us. In fact, because we’ve built this app using the --api version of Rails 5, the cookies functionality doesn’t even exist.

We’re going to go with the first solution I mentioned, a simple API key that is attached to the user’s account. When they request a page, they’ll include that in the request headers under the X-Api-Key value.

Here is what our User model looks like. It will assign each new user a key (which could later be regenerated and/or modified if we need to shut this account out because of abuse).

Inside of the ApplicationController, we’ll include a few small methods which will help us validate that the token is in fact correct, who it belongs to, and to respond with a 401 unauthorized response if it is invalid.

As far as I can tell, json:api doesn’t speak much or at all about authentication, and it is left up to the implementor to decide what works best for their specific API and its use case.

Documenting Your API

Part of what makes APIs great — such as the ones from Stripe, Twilio, or GitHub — is how good their documentation is. I won’t go into too much detail here other than to recommend two of the best approaches I’ve seen.

The first approach, a gem called apipie-rails, is done by adding extra details to your tests, which automatically generates documentation. This has the benefit of being easy to keep in sync with your code as it is right there alongside it, but I find that it can tend to clutter up the tests a little bit.

The second approach is one called slate which allows you to create some really beautiful API documentation, much like the ones I listed above. This is done in markdown, and it ends up generating static HTML/CSS files that you can host.

Conclusion

In this article, we expanded upon a previous article about creating JSON APIs within Rails. We tackled some common problems that occur when creating APIs, such as wanting pagination, sorting, errors, and authentication. We looked at how we can develop these features in keeping with the json:api spec.

By doing so, we avoid needing to have conversations about how to implement each feature, and we can instead focus on the specifics of our application.

Subscribe via Email

Over 60,000 people from companies like Netflix, Apple, Spotify and O'Reilly are reading our articles. Subscribe to receive a weekly newsletter with articles around Continuous Integration, Docker, and software development best practices.

We promise that we won't spam you. You can unsubscribe any time.

Join the Discussion

Leave us some comments on what you think about this topic or if you like to add something.

Another pretty simple way to do such a job is by using the jsonapi-utils (https://github.com/b2beauty/jsonapi-utils) gem that works on top of jsonapi-resources gem, taking advantage of its resource-driven style but bringing a Rails way to have JSON API-compliant APIs.

A small trick: To avoid DoubleRenderError when something in your code throws an error that gets rendered *after* you already rendered your response (for example in a callback or when you forget to add ‘return’ in code) just drop the whole response before rendering with self.response_body = nil

Gin Lennon

I don’t know why, but including `application/json` in the `api_mime_types` array breaks params fetching in the Controller. If I exclude it, all works fine. (Rails 5beta2, Ruby 2.3.0)

see my comment above about how you should not register the JSONAPI mime type this way and how you should instead register it

Ryan-Neal Mes

I am really loving your articles. Just thought I would share that the jsonapi matcher doesn’t work retrieving (GET request) of multiple items. I managed to get something similar working that handles multiple items.

I dont’ know what is the need of the “Format of data posted” section in this article, i mean the controller part not the specs, as the format already done in the previous article by adding the following line as initializer: