Yos Riady software craftsman 🌱

Building RESTful APIs with Rails

Building RESTful APIs with Rails

I seem to be building APIs left and right, so in this post, we’ll build a RESTful API with Ruby.

There are many approaches you can take when it comes to building an API with Ruby, such as the choice of using the grape DSL, Sinatra, or Rails. There's also design considerations to be made with regards to the language you write in. Node.js and Go is preferable when performance is a core requirement. As always, consider your requirements carefully when deciding on what to use to build your API.

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.

$ rails g controller api/api

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:

class API::APIController < ActionController::Base
# Authentication and functionality common to all API endpoint controllers.
end

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

ActiveSupport::Inflector.inflections(:en) do |inflect|
  inflect.acronym 'API'
end

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:

namespace :api do
    namespace :v1 do
        resources :agents
    end
end
Prefix            Verb   URI Pattern                       Controller#Action
api_v1_agents     GET    /api/v1/agents(.:format)          api/v1/agents#index
                  POST   /api/v1/agents(.:format)          api/v1/agents#create
new_api_v1_agent  GET    /api/v1/agents/new(.:format)      api/v1/agents#new
edit_api_v1_agent GET    /api/v1/agents/:id/edit(.:format) api/v1/agents#edit
api_v1_agent      GET    /api/v1/agents/:id(.:format)      api/v1/agents#show
                  PATCH  /api/v1/agents/:id(.:format)      api/v1/agents#update
                  PUT    /api/v1/agents/:id(.:format)      api/v1/agents#update
                  DELETE /api/v1/agents/:id(.:format)      api/v1/agents#destroy

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.

class API::V1::AgentsController < API::APIController

end

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:

namespace :api, defaults: {format: 'json'}, path: '', constraints: {subdomain: 'api'} do
    namespace :v1 do
        resources :agents
    end
end

For our purposes, we’ll focus on JSON and set it as the default return format.

Prefix            Verb   URI Pattern                   Controller#Action
api_v1_agents     GET    /v1/agents(.:format)          api/v1/agents#index
                  POST   /v1/agents(.:format)          api/v1/agents#create
new_api_v1_agent  GET    /v1/agents/new(.:format)      api/v1/agents#new
edit_api_v1_agent GET    /v1/agents/:id/edit(.:format) api/v1/agents#edit
api_v1_agent      GET    /v1/agents/:id(.:format)      api/v1/agents#show
                  PATCH  /v1/agents/:id(.:format)      api/v1/agents#update
                  PUT    /v1/agents/:id(.:format)      api/v1/agents#update
                  DELETE /v1/agents/:id(.:format)     api/v1/agents#destroy

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:

$ rails g migration AddApiKeyToUsers api_key:string
$ rake db:migrate

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:

class User < ActiveRecord::Base
  before_create :generate_api_key

  protected

  def generate_api_key
    self.api_key = loop do
      random_token = SecureRandom.urlsafe_base64(24, false)
      break random_token unless self.class.exists?(api_key: random_token)
    end
  end
end

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!

class API::APIController < ActionController::Base
  private

  def authenticate
    authenticate_or_request_with_http_token do |token, options|
      @user = User.where(api_key: token).first
    end
  end
end

Since our API resource controllers inherit from this base API::APIController, we can add authenticate as a before_action callback:

class API::V1::AgentsController < API::APIController
  before_action :authenticate

  def index
    <!-- Object serialization and response, next section -->
  end
end

Now, API endpoints that have authentication enabled will need an Authorization: Token HTTP header, or Rails will return an invalid token error:

curl -X GET -H "Authorization: Token token=UNJ-VDcj6vSS3gOn5l6xSpC5osuL-bn3" https://api.lvh.me:3000/v1/agents/1

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:

class AgentSerializer < ActiveModel::Serializer
  attributes :interval, :type, :name, :description, :user_id, :payload
end

We then render JSON in our API resource controller:

class API::V1::AgentsController < API::APIController
  # GET /agents
  def index
    @agents = @user.agents.page(params[:page])
    render json: @agents
  end
end

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:

{
  "links": {
    "self": "https://example.com/posts",
    "next": "https://example.com/posts?page[offset]=2",
    "last": "https://example.com/posts?page[offset]=10"
  },
  "data": [{
    "type": "posts",
    "id": "1",
    "title": "JSON API paints my bikeshed!",
    "links": {
      "self": "https://example.com/posts/1",
      "author": {
        "self": "https://example.com/posts/1/links/author",
        "related": "https://example.com/posts/1/author",
      },
      "comments": {
        "self": "https://example.com/posts/1/links/comments",
        "related": "https://example.com/posts/1/comments"
      }
    }
  }],

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:

class AgentSerializer < ActiveModel::Serializer
  attributes :name, :links

  def links
    {
      # Your discoverable actions
    }
  end
end

It’s easy to perform to cache serialized objects using active_model_serializers. Read more here. Note that you will need to have configured ActiveSupport::Cache::Store in your application.rb or config/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.

Author

Yos is a software craftsman based in Singapore.

📬 Subscribe to my newsletter

Get notified of my latest articles by providing your email below.


Going Serverless book

Interested to find out more about serverless? Going Serverless teaches you how to build scalable applications with the Serverless framework and AWS Lambda. You'll learn how to design, develop, test, deploy, and secure Serverless applications from planning to production.

Learn More →