Lean API on Heroku

We have recently been working on an iOS app that requires a backend service. In this case I was looking for something leaner, simpler, and faster than Rails since it was only an API for a mobile app, no web front-end. I wanted it to be very lean and scalable so we could make it through early user testing without spending a lot on servers. Ok, ideally without spending anything on servers; basically how much could I squeeze out of one Heroku worker.

After some investigation of the typical light weight ruby frameworks (Sinatra, Merb, etc) I stumbled upon Grape, a ruby API micro-framework. Grape uses a very simple DSL for defining APIs for your Ruby objects. Grape runs on Goliath, which is a non-blocking Ruby web server framework that uses Ruby fibers to simplify the handling of asynchronous requests. Goliath runs on the popular EventMachine reactor. EventMachine is an event-driven Ruby library that works much like Node.js.

The Grape github page provides a great deal of information on getting started, and David Jones has a great example project on GitHub demonstrating how to deploy a grape API on Heroku.

The DSL is really simple and I was able to wrap some ruby objects in a day and deploy it on Heroku. Here is an snippet of what the DSL looks like.

grape_example.rblink
require 'goliath'
require 'em-synchrony'
require 'em-synchrony/em-mongo'
require 'grape'
require './app/models/mission.rb'
require './app/models/player.rb'
module MyAppName
class API_v1 < Grape::API
prefix 'api'
version 'v1', :using => :header, :vendor => 'company_id' do
format :json
resource 'missions' do
# All mission API endpoints go here...
# ...
desc "Get a mission record using its id as key", {
:params => {
"id" => { :id => "Mission id.", :required => true }
}
}
get "/:id" do
error! 'Unauthorized', 401 unless env['HTTP_SECRETK'] == 'a secret key'
missions_json = env.missions_coll.find({'_id' => params[:id]})
error! 'Mission not found', 404 unless missions_json != []
mission = missions_json.map {|i| Mission.from_json(i)}.first
# map returns an array of 1 item, so get first as the mission object
# do something with the mission object here
# then return object to caller as json
mission.first.to_json
end
#...
resource 'players' do
# All player API endpoints go here...
# ...
desc "Get a player record using its id as key", {
:params => {
"id" => { :id => "Player id.", :required => true }
}
}
get "/:id" do
error! 'Unauthorized', 401 unless env['HTTP_SECRETK'] == 'a secret key'
players_json = env.players_coll.find({'_id' => params[:id]})
error! 'Player not found', 404 unless players_json != []
players = players_json.map {|i| Player.from_json(i)}
players.first.to_json
end
#...
end
end

As you can see the Grape framework supports API versioning, and the DSL provides a simple way to define API endpoints by defining resources and then specifying the HTTP requests (get, post, put, delete) for that resource. Any Rails developer should feel comfortable with the paths, params, and routes syntax in Grape.

In this code snippet I show part of the API definition for two resources (missions & players). This will mean the path for accessing these are /missions and /players. Because I specified prefix ‘api’ at the top of the file, the actual path becomes /api/missions and /api/players. So for example the get “/:id” would correspond to a get request of /api/players/7E781305-777F-4044-9770-C7995585F540 where the UUID is a player id and would be available in the params[:id] variable.

I should also note that even though these are simple get requests Grape will expect a header to be set for the secret key(secretk), and also require an Accept header with the version, vendor info, and content type (json). The easiest way to test your API will be to use an app like Postman that makes it easy to formulating requests, setting headers, from your Chrome browser. Postman also allows you to save requests, and re-run them.

I happen to be using Mongo for this, but Grape supports most major DBs. I am only showing two get methods, my full definition supports CRUD for these objects, as well as a few common queries of the db. The error checking of the request is fairly straightforward. I am checking for an API key, but this would be replaced with an API key lookup function to validate the requestor. It is easy to respond with specific errors as you can see.

The terse error!, unless syntax makes the error handling code very readable. The request handling for these two examples are simple: I query a mongo collection for an id, then map that response onto objects, and then convert the objects to json. This looks funny since it is basically json->object->json since mongo returns json, but there is more that happens in the object mapping; the json from the db and the json from the object are not the same.

By requiring mission.rb and player.rb at the top of the file, I pull in my Mission and Player classes so they can be used in the map operation. Try to ignore the lame .first operation, this was a bit of laziness in dealing with what is an array of one item resulting from the map operation. As with all Ruby, the output of the last operation is what is returned in the body of the response.

The DSL is very clean and makes API maintenance relatively easy.

A few other handy links:

Comments