API ror development

API with Ruby on Rails: useful tricks

| 24 Comments

This article is about gotchas you should be aware of while building your API with Ruby on Rails.

Controller tricks: API on Metal

Sooner or later each Rails developer come to a point when he wants to build his first API.
Among the first things you have to take care of are your controllers.
If you want your API to be fast (and I bet you do) then you should consider using ActionController::Metal.
The trick is that ActionController::Base has many Middlewares that are not necessary for the API, by using Metal controller with minimum modules included, the one can achieve up to 40% speedup.
Lets see what your basic metal controller may looks like:

Unfortunately NewRelic doesn’t support Metal by default, so you have to add monitoring manually.

Routing: use versioning

Nobody’s perfect. So are we.
Your API will definitely be changed and extended multiple times in the future so you better take care of your versioning at the beginning.
As you noticed, BaseController wrapped in V1 namespace. Use something like this in your routes:

Views tricks: RABL ‘em all

You don’t want to burden your code with logic of exposing different model fields for different API actions, right?
In this case you should use some template engine. RABL is at your service. Here’s an example of your view:

Also that will save you time by getting rid of ugly respond_to blocks. Instead of

You can simple do literally Nothing

Just make sure you have a RABL view in a corresponding directory.

Security

There’re plenty of articles about securing your API with OAuth. Another convenient way is to simply use token passed in the query string. If you ended up with token keep in mind that you can easily generate it by calling SecureRandom.urlsafe_base64. To make sure token is unique you can use something like this:

Hiding your IDs with GUIDs

By default Rails uses incremental integer for primary key.
Common practice is not to expose these kind of IDs to the public via your API because users can guess other IDs in your database and that might be a potential risk.
To solve this you can come up with a simple algorithm that will convert your IDs into some “safe” form and back.
But still it’s not super safe because someone can find out what the algorithm is.
Another possible solution is to expose GUIDs to the public.
It’s a 128 entity that typically looks like this:

To generate it in Ruby use SecureRandom.uuid (generates V4 GUID).
You can store it as a simple string column but if you are using Postgresql then you can utilize it’s built in uuid type – this can save you a lot of space.
Rails, however falls back to :string for the uuid Postgresql native type.
To workaround this you can create column manually in your migration (index should be added as well):

To use uuid_generate_v4 function you have to add Postgresql extension:

And don’t forget to change your schema format to :sql in config/application.rb

Testing your API

You definitely want to cover up your new shiny API with some sort of tests. See example below that uses Rack::Test::Methods

Conclusion

We’ve just gone through the following tricks:

  • Minimum usable Metal controller
  • Simple versioning
  • Easy API views with RABL
  • Postresql native uuid type
  • API spec sample

Sure, you’ll face a lot more issues in the wild but these basics intended to help you start up quickly.
The last, but not the least – don’t forget about good documentation. Your API users will definitely appreciate it.

If you want to try all these tricks in the wild see recently released API for our dummy SMTP server – Mailtrap.io

UPDATE
If you want to use render json: your_json you should include ActionController::Renderers::All in your base API controller. Thanks Divya Kunnath for pointing that out.

Share
* Railsware is a premium software development consulting company, focused on delivering great web and mobile applications. Learn more about us.
  • John Hinnegan

    Thanks. Always cool to see how others are building api’s

  • http://twitter.com/suaron Dmytro Piliugin

    improved build_token function

    def build_token
    begin
    token = SecureRandom.urlsafe_base64
    end while User.exists?(api_token: token)
    token
    end

    • http://twitter.com/gregolsent Innokenty Mikhailov

      Thanks! Post updated.

  • Alexander Korsak

    Probably for reading purpose you can use ‘type: :api’ instead of ‘api: true’. I haven’t tested this approach but it should definitely work properly.

    • http://twitter.com/gregolsent Innokenty Mikhailov

      Specifying ‘type: :api’ for the ‘describe’ block worked for me, but after updating to rspec 2.13.0 it works only if specifying for each ‘it’ declaration separately. That’s why I switched to ‘api: true’ which works for the wrapping ‘describe’

      • Paul

        I am Paul from Uganda, I need your help about creating ruby on rails server with an and client. My email: pomick.kawalya457@gmail.com

  • http://twitter.com/mattsolt Matt Solt

    Any advice for generating public HTML documentation for a Rails API?

  • http://twitter.com/vassilevsky Илья Василевский

    Rabl rendering is very slow for us. We see that it takes up to 50% of total response time. Are we doing something wrong? How fast Rabl is for you?

    • Innokenty Mihailov

      Indeed, Rabl is a bit sluggish. However I’ve tried to switch to rabl-rails https://github.com/ccocchi/rabl-rails which is intended to improve performance and it gave me ~50% speedup in rendering complex requests (for example rendering collection of 30 objects takes ~100ms with Rabl and ~50ms with Rabl-rails).

  • Innokenty Mihailov

    I think this commit is about removing Metal applications from Rails.
    Also it recommends:
    “For the future, you can use ActionController::Metal to get
    a very fast controller with the ability to opt-in to specific
    controller features without paying the penalty of the full
    controller stack.”

    And the ActionController::Metal is still present in Ruby on Rails master branch http://bit.ly/114MDgN.

  • Innokenty Mihailov

    Thanks for pointing, I’ll give it a look.

  • http://twitter.com/shime_rb Hrvoje Šimić

    If you like expressiveness of Sinatra when it comes to routes, you could also include Sinatrify inside your ActionController::Metal. Gist with example: https://gist.github.com/shime/5429106 https://github.com/shime/sinatrify#readme

  • denisjacquemin

    Hi, great post thanks, do you have a sample application using those tricks?

    • Innokenty Mihailov

      No sample application, only the sample blocks in the post.

  • alex li

    api: ture works for me. i have tried a lots with :type=> :api seems not working at all. this maybe related https://github.com/rspec/rspec-rails/issues/703

  • Pingback: Här är nya svenska API-katalogen! » API – Mashup.se

  • enricostn

    Great post, thanks!

    What about using https://github.com/rails-api/rails-api ?

  • Anthony

    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.

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

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

  • Divya Kunnath

    Render :json gives a template missing error. To fix this I had to add this module to the base controller
    include ActionController::Renderers::All

    Hope this helps someone :)

    • Innokenty Mihailov

      Thanks for pointing that out – I’ve updated the post.

  • Paulo Fabiano Langer

    my get request returns empty body but, accessing the url on the browser, I get the full json structure. Here is my spec:

    require ‘spec_helper’

    describe API::V1::EventsController do
    render_views

    describe “GET /api/events”, api: true do

    it “returns success” do
    get “/api/events”
    response.should be_success
    end

    it “returns events list” do
    get “/api/events”
    p response.body
    end

    end

    end

    The test result:

    .””
    .
    Finished in 0.11073 seconds
    2 examples, 0 failures
    Randomized with seed 2147

    • Innokenty Mihailov

      It’s hard to say w/o seeing your EventsController. Please, post all the elements (routes, controller, etc) and I’ll try to help you

  • ethicalhack3r

    FYI ‘append_view_path’ since Rails 4.1 is in AbstractController so will need to add “include AbstractController::Rendering” in Api::V1::BaseController

  • Pingback: List of 40 tutorials on how to create an API - Mashape Blog

Want to get more of Railsware blog?

RSS FEED

We're always ready to help!

CONTACT US