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.
Post by Kamil Bielawski
Kamil has been working with AmberBit since 2012, and is an expert on JavaScript and Ruby, recently working with Elixir code as well.