Glad You're Ready. Let's Get Started!

Let us know how we can contact you.

Thank you!

We'll respond shortly.

Building a fast, lightweight REST service with Rails 3

I recently started building a Rails 3 app that will function as an internal REST service. I wanted it to be as lightweight and fast as possible, both to test and to run. Here are a few ways I configured the app to be a bit faster:

  • Defined a limited route set
  • Removed ActiveResource
  • Removed unnecessary middleware
  • Created a custom controller that inherits from Metal

First, I defined the public api for my app in the routes file. I chose not to use resources because

  • I don’t need named routes
  • I wanted the :id parameter for the show action to be named :account_id instead of id (a pet peeve of mine with Rails routing)
  • I only needed 3 of the routes generated by resources

The routes file ended up looking like this:

scope "/api/v1" do
  scope "/accounts" do
    post "/" => "accounts#create"
    get ":account_id" => "accounts#show"
    get ":account_id/transactions" => "accounts#transactions"
    put ":account_id" => "accounts#update"

Next, to get rid of ActiveResource, in application.rb I deleted the line the requires rails/all and replaced it with:

# config/appliction.rb
require "rails"

).each do |framework|
    require "#{framework}/railtie"
  rescue LoadError

Rails has a lot of middleware that I didn’t need for a stateless service like this. This app will be run as a single-threaded app, behind a firewall, with no session or cookies, no views, it will only be accessed via an HTTP library and I’ll likely never open it in a browser even locally. As such, I added this to my application file:

# config/application.rb
].each do |klass|
  config.middleware.delete klass

# config/environments/production.rb
config.middleware.delete ActiveRecord::ConnectionAdapters::ConnectionManagement

I was surprised by that last one. It’s a rack middleware class sitting in ActiveRecord whose sole purpose is to not close connections in test mode. It seems to me that Rails should just include that class in environments/test.rb, as opposed to having it in every environment. It probably doesn’t matter much, since it’s only going to add a few nanoseconds to each request.

I didn’t need any view rendering at all, so I deleted app/views and app/helpers, then created my own ApplicationController class with just the modules I needed:

class ApplicationController < ActionController::Metal
  include AbstractController::Logger
  include Rails.application.routes.url_helpers
  include ActionController::UrlFor
  include ActionController::Rendering
  include ActionController::Renderers::All
  include ActionController::MimeResponds

In my case this was the minimum set of modules necessary to make my tests pass. Unfortunatley, these modules are still very poorly documented, so to figure out which ones I needed I had to:

  • Copy all the code from ActionController::Base into my ApplicationController
  • Delete modules one by one, testing after each one was removed and only leaving in the ones that broke the tests when they were removed

In my controller I just used render :json => object, as opposed to the new respond_to because I don’t really care to check the accept header for every request. For now there will only be json responses, regardless of what you ask for, so I didn’t bother with adding the additional overhead. Here’s what an action looks like:

class AccountsController < ApplicationController
  def show
    render :json => Account.find(params[:account_id])

I normally use cucumber for integration testing, but for an app like this I decided to use rspec 2’s new request spec format, which is lighter but still exercises the stack. It looks something like this:

# spec/requests/api_spec.rb
require 'spec_helper'
describe "api" do
  describe "GET /api/v1/accounts/:account_id" do
    it "returns a json hash with the proper data" do
      get "/api/v1/accounts/abc123"
      Yajl::Parser.parse(response.body).should == { "id" => "abc123", "billing_date" => "12/12/2009" }

In the end, this has translated to slightly faster load times, faster test runs and faster responses in production without altering the things I like best about Rails.

  1. Will Bryant says:

    Above you wrote “I was surprised by that last one. It’s a rack middleware class sitting in ActiveRecord whose sole purpose is to not close connections in test mode.”

    The purpose of ConnectionManagement is to close the database connection if an exception is raised, so that the next request will get a new connection rather than checking the old, potentially permanently broken, connection out from the pool and trying to use it again.

    It’s essential _not_ to do this in test mode because it would mess up the “transactional fixtures” mechanism (which is normally used/useful whether or not you use fixtures) whenever you try something that raises an exception, which is obviously not desirable. Since connections are not normally left permanently broken by your tests (or at least, it’s very unusual – and won’t cause an actual service outage), this middleware deactivates itself in that case.

    That doesn’t mean it’s purpose is related to test mode – actually quite the opposite.

    This middleware does perform a very useful function in real-world apps, if you want your app to be able to recover from things going wrong at runtime (a database server crash/restart, for example) without you or a supervisor process having to go in and restart the rails app. I suggest you don’t delete it from the chain.

Post a Comment

Your Information (Name required. Email address will not be displayed with comment.)

* Copy This Password *

* Type Or Paste Password Here *