Building and documenting API in Rails

These are really exciting times for web developers. New JavaScript frameworks emerge constantly, completely changing the way we think about building web applications. With libraries such as AngularJS, Ember.js or Backbone.js, the server is no longer responsible for generating complete pages. Its role is often reduced to serving as a backend for client-side application. This approach has many advantages. To name a few:

  • it allows better separation between presentation and business logic
  • better performance for the user - instead of reloading the whole page, only parts that need to change are replaced
  • reduced resources usage - fetching only small chunks of data instead of rendering whole pages, as well as pulling large parts of business logic into web browser, can be a huge relief for your web server and as a result - for your pocket
  • it can save you substantial amount of work if you’re creating multiple clients, for example website and native mobile clients for iOS, Android etc. A good, well-defined API can be reused for all these purposes.

You might have doubts if large, full-featured framework like Ruby on Rails is a right tool for building such backend. Indeed, it might look like an overkill. However, I believe it is not. With just a little bit of configuration and some useful gems, we can disable the parts that we don’t need and still use all the Rails’ goodness we’re used to. And, while it might not be obvious at first sight, many Rails features are applicable to API applications:

  • routing - resourceful routes lie at the heart of a good REST API. Rails router makes it super easy co create them effortlessly
  • easy testing - testing is vital part of software development, and API applications are no different.
  • rich library of third-party plugins - no matter what functionality your application needs, chances are that someone already implemented it
  • convenient defaults for development mode
  • logging
  • and much more

In this article we’ll have a look at creating an API using Ruby on Rails. We’ll try to cover some of the most common issues one can encounter when developing such application. For illustrative purposes we’ll use a sample application, which allows tracking projects user participates in.

Getting Started

When creating an API application we don’t need all the features that Rails provides out of the box. For example, we’re not interested in generating templates on the server. Picking the features needed for building an API is trivial when using Rails::API gem.

We start by adding appropriate entry to our Gemfile:

  gem 'rails-api'

and running bundle install. Next, we want our controllers to inherit from ActionController::API. To achieve this, let’s edit app/controllers/application_controller.rb file. Change

 class ApplicationController < ActionController::Base
    protect_from_forgery
    ...
  end

to

  class ApplicationController < ActionController::API
    ...
  end

As you might have noticed, we also removed protect_from_forgery call.

Alternatively, if you’re creating your application from scratch, you can install rails-api as a standalone gem

  $ gem install rails-api

and generate new project using

  $ rails-api new my_api

Rails::API includes sensible set of middlewares enabled by default. It’s possible to add the ones that are inactive. For example to add Session Management, you simply need to add following line in config:

  config.middleware.use Rack::SessionManagement

Middlewares can be disabled with following code:

  config.middleware.delete Rack::SessionManagement

Full documentation for Rails::API can be found here.

Versioning

To be of any value, API must remain stable and consistent. If you introduce any changes to the routing, parameters passed to the API or response format, clients using the API might break. The above statement is especially true for publicly available APIs, which can be used by large number of third-party clients. On the other hand, evolution of software is completely natural and inevitable. How can we reconcile these contradictions? API versioning to the rescue! Thanks to versioning, you can release new, improved API, without cutting off clients using the old one. Let’s set up routes for our versioned API: config/routes.rb

  MyApp::Application.routes.draw do
    namespace :api do
      namespace :v1 do
        resources :user
        ...
      end
    end
  end

Next, we need to create api/v1 directory under app/controllers and put our controllers there: app/controllers/api/v1/users_controller.rb

  module Api
    module V1
      class UsersController < ApplicationController
        ...
      end
    end
  end

If at some point in the future we decide to introduce new version of our API, we can simply create new namespace for it, like this:

  MyApp::Application.routes.draw do
    namespace :api do
      namespace :v1 do
        resources :user
        ...
      end

      namespace :v2 do
        resources :user
        ...
      end
    end
  end

and create controllers structure analogous to the existing one.

Rendering Response

There are many formats, in which communication with the API can be performed. The most popular choices are XML and JSON. For our sample application we will use the latter exclusively. JSON, or JavaScript Object Notation, has its origins in JavaScript, but is widely supported by other programming languages, including Ruby. JSON’s main advantage over XML is simpler syntax, which makes it easier to read by human and faster(both in parsing and - due to smaller size - transmission over the Internet). Thanks to built-in support for JSON, creating response in this format in Ruby on Rails is a breeze:

  class ProjectsController < ApplicationController
    ...
    def index
      ...
      render json: {message: 'Resource not found'}
    end
    ...
  end

This way works perfectly fine for simple responses. However, if there’s more data you would like to return, you controller might get pretty fat. For these cases, we can use one of available DSLs. The one I find most convenient is Jbuilder, enabled by default in Rails 4. If you’d like to use it with older version of Rails, you might need to uncomment it in the Gemfile (or add it altogether, for really old versions):

  # To use Jbuilder templates for JSON
  gem 'jbuilder'

It helps you by adding support for loops and conditional statements, as well as partials. Let’s see all that in action! Jbuilder files are treated like a regular template files: you put them in app/views directory. For example, let’s have a look at template for single project: app/views/api/v1/projects/show.json.jbuilder

  project ||= @project

  json.id project['id']
  json.name project['name']
  json.source_name project['source_name']
  json.source_identifier project['source_identifier']
  json.task_count project['task_count']
  if project.class == Hash
    json.active
  Project.find(project['id']).active_for_user?(@api_key.user)
  else
    json.active project.active_for_user?(@api_key.user)
  end

  if project.class == ActiveRecord::Base && !project.persisted? &&
  !project.valid?
    json.errors project.errors.messages
  end

Now, let’s see how we can use this template as a partial, to return all projects user is assigned to: app/views/api/v1/projects/index.json.jbuilder

  json.projects @projects, partial: 'api/v1/projects/show', as: :project
  json.total_count @projects.respond_to?(:total_entries) ?
  @projects.total_entries : @projects.to_a.count

Above template will result in following JSON:

  {
    "projects": [ 
      { "project": {
                     "id":17,
                     "name":"Death Star contruction",
                     "source_name":"Pivotal Tracker",
                     "source_identifier":"796315",
                     "task_count":188
                   }
      },
      { "project": {
                     "id":77,
                     "namespaceme":"Get The Ring to Mordor",
                     "source_name":"Pivotal Tracker",
                     "source_identifiere_identifier":"123456",
                     "task_count":4
                   }
      }
    ], "total_entriesl_count":2
  }

Of course, Jbuilder is not the only option you have. I suggest also having a look at RABL.

Response from an API is not complete without correct HTTP response code. To set status code in you controller, just pass :status key to render method, like this:

  render json: {message: 'Resource not found'}, status: 404

You can also use symbols instead of numbers:

  render json: {message: 'Resource not found'}, status: :not_found 

Full list of status codes can be found here, but you’re most likely to use only limited subset of them:

  • 200 - :ok
  • 204 - :no_content
  • 400 - :bad_request
  • 403 - :forbidden
  • 401 - :unauthorized
  • 404 - :not_found
  • 410 - :gone
  • 422 - :unprocessable_entity
  • 500 - :internal_server_error

One last improvement to the way we handle response formats is setting JSON as the default. We can do so in config/routes.rb file:

  namespace :api, defaults: {format: 'json'} do
    namespace :v1 do
      ...
    end
  end

This way, when sending request to

  http://www.example.com/api/v1/projects.json

we can omit the format:

  http://www.example.com/api/v1/projects

Authentication

An API is a gateway to your application. In most cases you don’t want to allow everyone to access, edit and remove data. You need to make sure that only authorized users will be able to alter it. One way to secure your API is by Access Tokens. We’ll create a separate model, called ApiKey, to store token. The token itself will consist of 32 hexadecimal characters. Let’s have a look at ApiKey class:

  class ApiKey < ActiveRecord::Base

    attr_accessible :user, :token

    belongs_to :user

    before_create :generate_token

    private

    def generate_token
      begin
        self.token = SecureRandom.hex.to_s
      end while self.class.exists?(token: token)
    end

  end

The class is very simple. It has a callback method generate_token, which creates unique string of hexadecimal characters when the class is instantiated. Next, let’s make sure that every user will be assigned his api key upon registration

app/models/user.rb

  class User < ActiveRecord::Base
    ... 
    has_one :api_key, dependent: :destroy

    after_create :create_api_key
    ...
    private

    def create_api_key
      ApiKey.create :user => self
    end
  end

Now, when we receive a request, we need to check if it contains correct token and find the current user based on it. We do it in the Application controller app/controllers/appliation_controller.rb

  include ActionController::HttpAuthentication::Token::ControllerMethods
  include ActionController::MimeResponds

  class ApplicationController < ActionController::API

    private

    def restrict_access
      unless restrict_access_by_params || restrict_access_by_header
        render json: {message: 'Invalid API Token'}, status: 401
        return
      end

      @current_user = @api_key.user if @api_key
    end

    def restrict_access_by_header
      return true if @api_key

      authenticate_with_http_token do |token|
        @api_key = ApiKey.find_by_token(token)
      end
    end

    def restrict_access_by_params
      return true if @api_key

      @api_key = ApiKey.find_by_token(params[:token])
    end

  end

We support passing acces token both as a header and parameters. To support header authentication, we need to include ActionController::HttpAuthentication::Token::ControllerMethods module, which is disabled by Rails::API by default. Now, we need to make sure that the token will be checked with each call to the API. We’ll add before_filter to our controllers:

 before_filter :restrict_access 

Testing

When testing an API, you should test both response body and HTTP status codes. An example of test that checks both: spec/api/tasks_api_spec.rb

  require 'spec_helper'
  require 'api/api_helper'
  require 'fakeweb'
  require 'timecop'

  describe 'Tasks API' do

    before :each do
      FactoryGirl.create :integration
      FactoryGirl.create :project
      FactoryGirl.create :task
      Project.last.integrations << Integration.last
    end

    # GET /tasks/:id
    it 'should return a single task' do
      api_get "tasks/#{Task.last.id}", {token: Integration.last.user.api_key.token}
      response.status.should == 200

      project = JSON.parse(response.body)
      project['id'].should == Task.last.id
      project['project_id'].should == Task.last.project_id
      project['source_name'].should == Task.last.source_name
      project['source_identifier'].should == Task.last.source_identifier
      project['current_state'].should == Task.last.current_state
      project['story_type'].should == Task.last.story_type
      project['current_task'].should == Task.last.current_task
      project['name'].should == Task.last.name
    end
    ...

Above test uses helper method api_get, which constructs a request and returns parsed response body: spec/api/api_helper.rb

  def api_get action, params={}, version="1"
    get "/api/v#{version}/#{action}", params
    JSON.parse(response.body) rescue {}
  end

  def api_post action, params={}, version="1"
    post "/api/v#{version}/#{action}", params
    JSON.parse(response.body) rescue {}
  end

  def api_delete action, params={}, version="1"
    delete "/api/v#{version}/#{action}", params
    JSON.parse(response.body) rescue {}
  end

  def api_put action, params={}, version="1"
    put "/api/v#{version}/#{action}", params
    JSON.parse(response.body) rescue {}
  end

Documenting with RDoc

If you want your API to be publicly available, it’s important to document it well. There’s no single recipe for writing a good documentation, but you should consider putting following info in it:

  • description, saying in short what’s the purpose of given method
  • list of required and optional parameters that can be passed to the method
  • possible response codes
  • format of response
  • sample calls

To generate the documentation we used RDoc, which allows you to create documentation based on the source code.

  module Api
    module V1
      class ProjectsController < ApplicationController
        ...
        ##
        # Returns a list of recent projects for a given user
        #
        # GET /api/v1/projects/recent
        #
        # params:
        #   token - API token
        #
        # = Examples
        #
        #   resp = conn.get("/api/v1/projects/recent", "token" => "dcbb7b36acd4438d07abafb8e28605a4")
        #
        #   resp.status
        #   => 200
        #
        #   resp.body
        #   => [{"project": {"id":1, "name": "Sample project", "source_name": "Pivotal Tracker", "source_identifier": "123456", "task_count": "2", "active": true}},
        #       {"project": {"id":3, "name": "Some random name" "source_name": "GitHub", "source_identifier": "42", "task_count": "0", "active": true}}]
        #
        #   resp = conn.get("/api/v1/projects/recent", "token" => "dcbb7b36acd4438d07abafb8e28605a4")
        #
        #   resp.status
        #   => 404
        #
        #   resp.body
        #   => {"message": "Resource not found"}
        #
        def recent
          @projects = Project.recent(@current_user)

          if @projects[0].present?
            render 'index'
          else
            render json: {message: 'Resource not found'}, status: 404
          end
        end
        ...
      end
    ebd
  end

The syntax for formatting your comments is very simple. Here are some basic rules, that you’ll find most useful:

  • lines starting with = are headers. The number of = characters defines the level of heading, so

    = Header

    will result in following HTML:

    <h1>Header</h1>

    , while

    == Header

    will give you

    <h3>Header</h3>

  • hyphen and asterisk are items of unordered list
  • digit or a letter followed by a dot are itams of ordered list
  • for labelled list, wrap labels in square brackets or append two colons to them

    [label] description another:: description 2

  • to alter text style, use bold, italics or +monospace+
  • any lines starting with larger indentation than the current margin are treated as verbatim text. It’s great for embedding code snippets with syntax highlighting inside the documentation

It’s also possible to use different markup format, than the default RDoc::Markup. RDoc has built in support for markdown, rd and tomdoc.

More in-depth reference for Rdoc syntax, as well as another markup formats can be found in project documentation

To generate documentation, run rdoc command in project main directory. It will store html files containing newly created documentation inside doc directory. You can also define index page for a documentation. In our case, the command to generate documentation will be

rdoc --main README.md

Summary

We covered basic steps required to build your own REST API with Ruby on Rails. It was by no means comprehensive guide, but it should be enough to get you started on yout way to mastering the art of building great APIs.

by Kamil Bielawski

comments powered by Disqus