Building APIs With Rails

May 19, 2011 — Code

Yesterday at RailsConf Yehuda Katz gave an excellent talk on API design with Rails. He gave some good advice, including:

  • Use Rails. Most people don’t realize how much Rails does for them and try to use Sinatra instead. Don’t do it.
  • Always include keys at the root level that explain what the data is. (That is, don’t just return a bunch of attributes. Note that valid XML requires this but somehow people forget that it’s a good idea when they start using JSON.)
  • Don’t use nested resources because there’s no way to automatically infer the URL for a given object if it’s nested.
  • Be consistent with your attribute names.

The talk was in his usual straightforward and well-reasoned style and I found myself agreeing with most of it. However, there were a couple points on which I did not agree, which I would like to summarize here.

Naming Conventions

When talking about naming consistency, Yehuda explained that certain conventions don’t yet exist so you need to just pick something and be consistent throughout your API. He gave this example: let’s say your API reponds with a JSON representation of an article with associated comments. When you render the comments attribute for each article object do you include the attributes for each comment or do you just print an array of comment ids?


// 1. comments = array of comment IDs
{
  "articles": [
    {
      "id": 32,
      "title": "...",
      "body": "...",
      "comments": [5, 12, 14]
    }
  ]
}

// 2. comments = array of full comment objects
{
  "posts": [
    {
      "id": 32,
      "title": "...",
      "body": "...",
      "comments": [
        { "id": 5,  "body": "..." },
        { "id": 12, "body": "..." },
        { "id": 14, "body": "..." }
      ]
    }
  ]
}

To me, the first approach is wrong. Why? Because the attribute is called comments and, with ActiveRecord, if you load an Article object and call its comments method you get an array of Comment objects, not just ids. If you want just the ids you call comment_ids. So if you print just the ids in your API you should call the attributre comment_ids. This is how ActiveRecord works, and in the course of building several APIs I’ve found that if we just follow ActiveRecord’s existing conventions we already have solutions to most of the problems we will encounter.

Another example: let’s say we have an API that returns articles with a comment_ids attribute (array of IDs) by default. We choose this as the default because in 90% of our API calls we don’t need access to comment details, but what about the 10% of requests for which we do? Well, let’s ask ourselves: “What would ActiveRecord do?” If you want to load articles with their full associated comment objects you do it like this:

Article.includes(:comments)

and we can get very close to that in our API:

/articles?includes=comments

Since APIs are often an interface to ActiveRecord written for programmers who already know ActiveRecord I see no reason not to mimick ActiveRecord as closely as possible in our API design. Even if the API clients will not be written by developers familiar with ActiveRecord, why not go with a familiar and successful solution? The problems of fetching data via an API are not that different from the problems of fetching data from a database. It seems like it should be possible to create a set of rules for translating ActiveRecord expressions (including includes, where, and custom scopes) into query-string compatible API calls, but I leave this exercise to the reader (and potential gem author).

Bulk Operations

Yehuda also covered the bulk_api gem in his talk, which allows you to act on multiple objects in a single API call through a URL like this:

POST /bulk/api

The request body should contain a JSON representation of the objects to be created, modified, or deleted. A lot of people don’t like this because it’s not RESTful. It also doesn’t follow any ActiveRecord-like convention. However, as someone who’s added an update_multiple action to a controller on more than one occasion I will say that I strongly prefer the approach of the bulk_api gem. It doesn’t follow ActiveRecord because there’s nothing to follow: ActiveRecord does not provide a good way to do bulk updates. It’s also better than adding an action to each existing controller because it allows you to work with different types of objects in a single request, and the routing is simpler (one route, as opposed to an additional route for every controller).

Furthermore, I would suggest that a RESTful approach is possible (and desirable) with some modification to the bulk_api gem. What if we think of a resource called a BulkUpdater, and we “create” a new BulkUpdater when we want to make changes in bulk? Then we’d have actions like these:

  • POST: create a new BulkUpdater (which starts running immediately)
  • GET: display status of a running BulkUpdater (for long-running updates)
  • DELETE: cancel bulk changes

This would support polling the server to check on the progress of a bulk request. It would also not bind the action to be performed on the objects to the HTTP method used for the request, allowing different actions to be performed on different objects in the same request.

Note that I’m not yet very familiar with the bulk_api gem so take this for what it’s worth. Corrections and other feedback welcome.


comments powered by Disqus