Building Awesome Rails APIs: Part 1

After teaching an Advanced Rails class last week, I realized that we use a lot of patterns internally to build great APIs in Rails that many people don’t know about. We didn’t invent most of them, but we use them with great success, so we need to start sharing.

Namespace your API

A really easy way to keep your API code clean is to namespace it. Give it its own controllers and routes. It is simple, and keeps your APIs independent from the rest of your controllers.

We start by adding this in our routes, assuming Person is the resource (model) we’re working with:

namespace :api do
  resources :people
end

This now gives us the standard routes for people, but namespaced as /api/people. It will look for a PeopleController in app/controllers/api/people_controller.rb.

class Api::PeopleController < ApplicationController

end

This is great on its own, but we can even fix that ugly Api by using Rails’ built-in inflections. Over in config/initializers/inflections.rb you can use the commented out acronym lines to convert Api to API.

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

This is a small change, but now our controller can use the API module, which feels a bit better.

class API::PeopleController < ApplicationController

end

Now you’re free to start building out your controller in a nice, RESTful way.

Versioning

API versioning can be controversial1, but if you want to add version info into your URLs, simply add another namespace to your routes:

namespace :api do
  namespace :v1 do
    resources :people
  end
end

Now your version 1 API is at URLs starting with /api/v1/ and your controller would be in a v1 folder with the added v1 module.

class API::V1::PeopleController < ApplicationController

end

You could now support multiple versions at the same time, just make sure you have a very good test suite so you don’t break v1 when you go to v2.

API Subdomain

When possible, I recommend serving your API on a separate domain as it lets you load balance traffic at the DNS level2. A quick change to our routes file limits it to only being served on a specific subdomain.

namespace :api, :constraints => {:subdomain => "api"} do
  namespace :v1 do
    resources :people
  end
end

Unfortunately, now out URls look like: http://api.example.com/api/v1/people. We can remove the duplication of “api” by setting a blank path:

namespace :api, :path => "", :constraints => {:subdomain => "api"} do
  namespace :v1 do
    resources :people
  end
end

Now our URls look like: http://api.example.com/v1/people or just http://api.example.com/people if you don’t use the version, it doesn’t interfere with your regular people routes, and it looks great.

Default to JSON

Most new APIs only need to serve JSON, yet it is common to see respond_to in API controllers:

class API::V1::PeopleController < ApplicationController

  def index
    @people = Person.all
    respond_to do |format|
      format.json { render :json => @people }
    end
  end

end

We can drop the respond_to, but if you hit the url without .json you’ll see it thinking you’re using HTML in the logs.

Processing by API::V1::PeopleController#index as HTML

We can default the format in our routes to JSON:

namespace :api, :defaults => {:format => :json} do
  namespace :v1 do
    resources :people
  end
end

Now we’ll see it knows we want JSON in the logs:

Processing by API::V1::PeopleController#index as JSON

This isn’t a necessary step, but can keep you in a JSON world. A great benefit is that you no longer have to specify the format in your test suite.

We have more tips for building awesome APIs that we’ll be sharing, but let us know in the comments if there are specific questions you have.

1 I’m not trying to take a side here, just showing an easy way to do it via URLs.

2 If your API gets hammered, you can point the DNS for the API subdomain to another set of servers and scale it independently of your main app.


Check out our latest product, Dead Man’s Snitch for monitoring cron, heroku scheduler or any periodic task.

Dead Man's Snitch

Photo of Daniel Morrison

Among colleagues, Daniel is known for rambling about algorithm design or obscure academic terms, which he blames on his classical training in Computer Science. Among clients, Daniel is known for being honest and direct when trying to get clients to focus on value and efficiency, which he blames on his unwavering quest for…value and efficiency.

Daniel founded Collective Idea in 2005 to put a name to his growing and already full-time freelance work. He works hard writing code, teaching, mentoring, and speaking about the company’s processes and tools.

Comments:


Post a Comment

(optional)
(optional — will be included as a link.)
  1. Great post TY.  One thing I personally would like to learn is how to secure your API. What Authentication & Authorization systems would you deploy etc..

    Lee
    Lee
    June 14, 2013 at 3:44 AM
  2. I concur on Auth systems, it would be great to know what your thoughts are on what you would deploy, etc.

    June 15, 2013 at 0:14 AM
  3. Have you tried to use grape instead of rails actions?

    In the latest rails version it’s easy to mount rack like applications. you can create api folder under app. and write your api code out of the rails.

    June 19, 2013 at 13:11 PM
  4. Looking forward to part 2 :)

    June 20, 2013 at 9:40 AM
  5. Alexandr Korsak: I haven’t used grape myself, but it seems fine. I’m not sure it would add a lot for most use cases, but the code looks good.

    June 20, 2013 at 9:56 AM
  6. I like the idea of defaulting the format to JSON, however using this approach actually adds :format to the params too, this causes my routing tests to expect { :format => :json } (and so they all fail).  I wonder if there’s another way to do achieve the same effect?

    June 20, 2013 at 17:46 PM
  7. > I’m not trying to take a side here, just showing an easy way to do it via URLs.

    Uh huh. :)

    June 21, 2013 at 8:56 AM
  8. In response to “I realized that we use a lot of patterns internally to build great APIs in Rails that many people don’t know about. We didn’t invent most of them, but we use them with great success, so we need to start sharing.” I once heard somebody say that it is not where you take things from, but where you take them to! 

    June 24, 2013 at 8:16 AM
  9. Wishing for part 2!!! Great post…

    Jon Gillies
    Jon Gillies
    June 24, 2013 at 16:56 PM
  10. >  I haven’t used grape myself, but it seems fine. I’m not sure it would add a lot for most use cases, but the code looks good.

    It’s primary benefit (to me at least) is that by mounting a Grape API as a Rack app, you bypass most of the Rails response handing code. When I implemented an API for The Verge, I tested Grape against ActionController and Grape was significantly faster.

    June 28, 2013 at 15:19 PM
  11. Thanks! Looking forward to part 2. I’m curious about how to handle the views (jbuilder or rable?) or do you use custom serializations (http://railscasts.com/episodes/409-active-model-serializers?view=asciicast). 

    I’ve took a look at grape, but sometimes your api is closely tied to your rails app. You can use the same relations, validations, custom attributes, etc as the rest of your rails app. This saves a lot of time. I agree that when writing a public api, grapes speed would help and allows to think more clearly about your api, making it more robust. But most of the time, (atleast for us) apis are used internally together with for instance backbone.js to create an async interactive experience for the user.

    July 16, 2013 at 9:44 AM
  12. Great post,  looking forward to part 2

    July 19, 2013 at 15:52 PM
  13. This really helped me starting a new project. Hopefully part two will be soon? ;-)

    Craig Walsh
    Craig Walsh
    September 10, 2013 at 9:31 AM
  14. Great post! Thanks for sharing :)

    September 11, 2013 at 4:15 AM
  15. Many thanks.

    October 30, 2013 at 17:31 PM
  16. Thanks, this actually helped me a lot :-)

    November 06, 2013 at 16:10 PM
  17. Thanks, great article!
    Looking forward to the next part. :-)

    November 13, 2013 at 15:28 PM
  18. This is wonderful! Thanks for sharing this, totally going to follow your blog. The explanation and code examples in this tutorial got me going asap.

    Cheers!

    November 17, 2013 at 12:21 PM
  19. I deployed a Rails engine (packed as a gem) that is really useful to test APIs on rails. You just have to mount the engine and go to the url that you specified, i.e. “localhost:3000/api_explorer” to see it. It’s a way of documenting an API also, reading the webservices specification from a file.

    Any comments or help improving the api is welcome. :)

    Anthony
    Anthony
    November 21, 2013 at 14:02 PM
  20. By the way,

    the gem is named ‘api_explorer’ and the repo is in http://www.github.com/toptierlabs/api_explorer

    Anthony
    Anthony
    November 21, 2013 at 14:03 PM
  21. great post ! What is you opinion about using JBuilder?

    Alejandro
    Alejandro
    December 12, 2013 at 19:04 PM
  22. So that’s the trick. Anyways, great post. Can’t wait for part 2!

    Mark
    Mark
    December 19, 2013 at 4:43 AM
  23. not working for rails it a rises routing error friend

    madhu
    madhu
    December 31, 2013 at 1:36 AM
  24. Really useful post, actually we use most of the patterns that you show additonallly we use Rabl and backbone.js in frontend, thanks for sharing Daniel

    January 08, 2014 at 11:04 AM
  25. Useful post. Thank you. Looking Forward…

    Rakesh Haridas
    Rakesh Haridas
    February 22, 2014 at 2:17 AM
  26. Thanks for sharing the post, it is very useful.

    March 04, 2014 at 16:22 PM
  27. great post. waiting for part 2. 

    adi
    adi
    March 19, 2014 at 21:21 PM
  28. When is Part 2…. it has been a while?

    March 28, 2014 at 18:58 PM
  29. What is your strategy when the API and the Web controllers essentially do the same thing, albeit one responding with HTML and the other with JSON? I can see the advantage of namespacing and versioning the API — but the rest are almost identical. I suppose I could have a superclass for both initially and over time diverge if needed. 

    April 04, 2014 at 17:50 PM
  30. Martin: We use interactors to extract the behavior of a specific action into its own class, allowing that class to be reused across multiple controllers.

    http://github.com/collectiveidea/interactor

    Steve Richert
    Steve Richert
    April 04, 2014 at 18:36 PM
  31. I have a question, some way to add cascade versioning support ? I mean when you send a request, the api first find in v2 then in v1 before throws error ?

    April 07, 2014 at 10:36 AM
  32. Excellent article. I’d be very interested to hear how you deal with JSONP.

    June 05, 2014 at 8:49 AM
  33. Great article..learnt so many things from this.
    I think now in rails 4 we can directly render all the data using 

    @people = Person.all
    render json: @people

    dharshan
    dharshan
    June 23, 2014 at 3:37 AM
  34. Nice One!!

    Rakesh Chand Rajwar
    Rakesh Chand Rajwar
    July 09, 2014 at 7:10 AM
  35. @chebyte I think you can extend V1 at V2, then you don’t have to make all of V1’s methods. But you have to make an empty controller instead.

    Sunho
    Sunho
    August 04, 2014 at 5:33 AM
  36. Any plans for part 2?  Would be very interested to see how you handle security and authentication.

    Stijn Verrept
    Stijn Verrept
    August 18, 2014 at 18:49 PM
  37. Great post

    Guru
    Guru
    February 13, 2015 at 1:55 AM
  38. Als oprichter van Nutrogenics bied ik u via deze firma supplementen aan onder de
    merknaam WHC.

    May 19, 2015 at 9:44 AM
  39. Is there a part 2 to this program or are you done?

    John Robert Peters, Jr.
    John Robert Peters, Jr.
    May 22, 2015 at 4:45 AM