Building RESTful APIs with Rails
Sunday, 12 April 2015 · 27 min read · api-design ruby railsI seem to be building APIs left and right, so in this post, we’ll build a RESTful API with Ruby.
Let’s start with some theory on REST
and RESTful APIs. Then, we’ll work through building an API which supports the following:
- API Namespacing
- API Versioning
- API Request Authentication and API Keys
- Object serialization + caching
- HATEOAS
In Part 2, we will go into more detail on documentation, security, optimization, and testing.
Note that this guide is going to assume familiarity with the Rails framework, the Ruby programming language, and some familiarity with the Rails ecosystem.
What are the essential parts of a RESTful API ?
RESTful APIs embrace the principles that make the web great: flexibility, standardization, and loose coupling to any given service. They take into account the principles of systems design enumerated by Roy Fielding in his thesis summarized below:
- Representational state transfer: Your resource endpoints should return representations of the resource as serialized data, such as JSON. The returned information should be sufficient for the client to uniquely identify and manipulate the database row in question.
- Resource identification per request: Each request should uniquely identify a single resource. This means each request that modifies your database should act on one and only one row of one and only one table.
- HATEOAS: An abbreviation for Hypermedia as the Engine of Application State, this simply means that as an API client enters your API, all future actions the client may take are discovered within representations returned from the server through link relations. PayPal’s API is an example of an API with HATEOAS. More on HATEOAS.
- Statelessness: Unlike SOAP, client state should not be stored on the server between requests. Said another way, each individual request should have no context of the requests that came before it.
There are also models out there such as the Richardson Maturity Model which breaks down the principal elements of a REST approach. Read more about the model here.
Fulfilling all of the above will make for a RESTful API, at least in practice.
Base API Controllers
At this point, I should mention that if you’re building an API-only application without view rendering, I highly suggest using Rails::API instead of default Rails.
Rails::API
is a subset of a normal Rails application, created for applications that don’t require all functionality that a complete Rails application provides. It is a bit more lightweight, and consequently a bit faster than a normal Rails application. Anyway, whichever Rails you use the patterns you use for your API which we’ll go through in the rest of this post will remain the same.
Let’s start by creating a base APIController. This helps us avoid the ApplicationController as APIs do not need the basic controller functionalities like protection from request forgery (the CSRF token), session authentication and the view helpers.
Note that we’re creating our API controller in its own API namespace as API::APIController. Feel free to rename the base API controller to anything you like, such as api/base (API::BaseController). Which naming convention do you think is most clear? Let me know in the comments!
To ensure we get only basic controller functionality we need our API::APIController
to inherit directly from ActionController::Base. So, be sure to replace the code as follows:
This is the perfect place to put common authentication and helper methods used across your API resource controllers.
So I was reading Assembly’s source code, and came across a particularly clean way to handle HTTP error codes in your base API controller. I’ve since used this approach for my API controllers.
Note that to use the API
acronym in all-caps instead of the default Api
across all your controllers, add the following code in your config/initializers/inflections.rb
API Namespacing && Versioning
It’s a best practice to park your API at a dedicated API namespace. Let’s first define our routes. We’ll also add a version namespace. In routes.rb:
Our API now looks like https://example.com/api/v1/agents
which will point to an AgentsController
in app/controllers/api/v1/agents_controller.rb
if it exists.
Having a dedicated subdomain for API endpoints similar to api.github.com gives you more control to load balance your incoming requests on the DNS level. Let’s use an API subdomain:
For our purposes, we’ll focus on JSON and set it as the default return format.
Great! Now our API looks like https://api.example.com/v1/agents
. This prevents interference with existing non-API routes such as https://example.com/v1/agents
. Let’s move on to API keys and authentication.
API Keys
I’m going to assume that you have an existing User
model. Let’s add an API key column for your users
table:
We’ll also want to automatically generate API Keys for newly created users, and for that we can use a before_create
callback. In your user.rb
model definition:
There’s also a new
has_secure_token
ActiveRecord class annotation that you can use instead of writing your own token generation method. It supports auto-generation as well as re-generation of tokens. However at the time of writing, this feature is not yet in Rails’ latest stable branch. In the meantime you can just look at it as reference. Moving forward, it’s cleaner to use this once it’s on a stable release.
API Request Authentication
The simplest way to do API authentication in Rails is to use the inbuilt method authenticate_or_request_with_http_token
:
At this juncture, I want to briefly mention a free API testing tool called Postman. It’s a standalone app that lets you easily build collections of API endpoints complete with request parameters. I use it to test my APIs all the time. Beats testing endpoints with cURL!
Since our API resource controllers inherit from this base API::APIController, we can add authenticate as a before_action
callback:
Now, API endpoints that have authentication enabled will need an Authorization: Token
HTTP header, or Rails will return an invalid token error:
There are other ways to perform API authentication, such as passing the API key in request.headers['X-Api-Key']
or as a query parameter:
https://api.lvh.me:3000/v1/agents/1?api_key=UNJ-VDcj6vSS3gOn5l6xSpC5osuL-bn3
But honestly, this seems wrong. The params will get logged and the API keys would be seen in the logs. Better to avoid this situation and pass the API key in the request header. I also think using a single method of authentication is more than sufficient. Let me know what you think!
Object Serialization
To fulfill Representational State Transfer, our resource endpoints should return representations of the resource as data. This means we will have to serialize our objects into either XML or JSON.
Many Rails APIs opted to treat JSON as another kind of view and construct views for each JSON response using gems such as jBuilder
. However, by going through Rails’ view generation middleware, your API suffers in terms of performance.
Using active_model_serializer serializer classes completely bypasses view generation, leading to better performance.
active_model_serializer
generates JSON from your ActiveRecord models.
We first create serializers for our API resources:
We then render JSON in our API resource controller:
Be sure to handle errors and exceptions in your API resource controller and returning the appropriate HTTP code!
active_model_serializer
by convention calls render json: @agents, serializer: AgentsSerializer
. To user a differently-named serializer, explicitly set the serializer
parameter in render
.
If you’ve ever argued with your team about the way your JSON responses should be formatted, jsonapi.org is a great standard.
HATEOAS
We’re using Hypertext, fine, that makes sense. But what’s it mean to be an engine? And application state?
Your application is just a finite state machine.
This is exactly how the Web works. Think about it. You start off on the homepage. That’s the only URL you have to know. From there, a bunch of links point you towards each state that you can reach from there. People would consider it ludicrous if they had to remember a dozen URLs to navigate a website, so why do we expect the consumers of our APIs to do so as well? –Steve Klabnik (Haters gonna HATEOAS)
Your APIs should do this. There should be a single endpoint for the resource, and all of the other actions you’d need to undertake should be able to be discovered by inspecting that resource. Here’s an example from the jsonapi.org standard:
Notice the links
key value pairs. When we GET a particular resource, we discover where we can go next
. It’s a discoverable action. The particular state we’re in shows what other states we can reach from here. This enables frontend clients consuming our API to potentially create the UI dynamically based off hypermedia links.
Using active_model_serializer
, HATEOAS can easily be achieved as follows:
It’s easy to perform to cache serialized objects using
active_model_serializers
. Read more here. Note that you will need to have configuredActiveSupport::Cache::Store
in yourapplication.rb
orconfig/environments/*.rb
. You can use memcached with the dalli gem.
The beauty of HATEOAS is that it allows you to interact and construct an API flow solely through the hyperlinks returned in the API response. You no longer need to hard code logic into your client in order to use an API. HATEOAS links are provided for each call and for transactions within a call, if available.
And that’s it! You have a 100% RESTful API!
Summary
I hope you managed to learn something from this post. With what you’ve seen so far, you should be able to comfortably get started with writing RESTful Rails APIs that are:
- Properly namespaced
- Open to versioning
- Authenticated
- Representational
- Discoverable (HATEOAS)
Let me know what you think in the comments below.
📬 Get updates straight to your inbox.
Subscribe to my newsletter so you don't miss new content.