Freelancing Gods 2014

God
24 Sep 2011

Versioning your APIs

As I developed Flying Sphinx, I found myself both writing and consuming several APIs: from Heroku to Flying Sphinx, Flying Sphinx to Heroku, the flying-sphinx gem in apps to Flying Sphinx, Flying Sphinx to Sphinx servers, and Sphinx servers to Flying Sphinx.

None of that was particularly painful – but when Josh Kalderimis was improving the flying-sphinx gem, he noted that the API it interacts with wasn’t that great. Namely, it was inconsistent with what it returned (sometimes text status messages, sometimes JSON), it was sending authentication credentials as GET/POST parameters instead of in a header, and it wasn’t versioned.

I was thinking that given I control pretty much every aspect of the service, it didn’t matter if the APIs had versions or not. However, as Josh and I worked through improvements, it became clear that the apps using older versions of the flying-sphinx gem were going to have one expectation, and newer versions another. Versioning suddenly became a much more attractive idea.

The next point of discussion was how clients should specify which version they are after. Most APIs put this in the path – here’s Twitter’s as an example, specifying version 1:

https://api.twitter.com/1/statuses/user_timeline.json

However, I’d recently been working with Scalarium’s API, and theirs put the version information in a header (again, version 1):

Accept: application/vnd.scalarium-v1+json

Some research turned up a discussion on Hacker News about best practices for APIs – and it’s argued there that using headers keeps the paths focused on just the resource, which is a more RESTful approach. It also makes for cleaner URLs, which I like as well.

How to implement this in a Rails application though? My routing ended up looking something like this:

namespace :api do
  constrants ApiVersion.new(1) do
    scope :module => :v1 do
      resource :app do
        resources :indices
      end
    end
  end

  constraints ApiVersion.new(2) do
    scope :module => :v2
      resource :app do
        resources :indices
      end
    end
  end
end

The ApiVersion class (which I have saved to app/lib/api_version.rb) is where we check the version header and route accordingly:

class ApiVersion
  def initialize(version)
    @version = version
  end

  def matches?(request)
    versioned_accept_header?(request) || version_one?(request)
  end

  private

  def versioned_accept_header?(request)
    accept = request.headers['Accept']
    accept && accept[/application\/vnd\.flying-sphinx-v#{@version}\+json/]
  end

  def unversioned_accept_header?(request)
    accept = request.headers['Accept']
    accept.blank? || accept[/application\/vnd\.flying-sphinx/].nil?
  end

  def version_one?(request)
    @version == 1 && unversioned_accept_header?(request)
  end
end

You’ll see that I default to version 1 if no header is supplied. This is for the older versions of the flying-sphinx gem – but if I was starting afresh, I may default to the latest version instead.

All of this gives us URLs that look like something like this:

http://flying-sphinx.com/api/app
http://flying-sphinx.com/api/app/indices

My SSL certificate is locked to flying-sphinx.com – if it was wildcarded, then I’d be using a subdomain ‘api’ instead, and clean those URLs up even further.

The controllers are namespaced according to both the path and the version – so we end up with names like Api::V2::AppsController. It does mean you get a new set of controllers for each version, but I’m okay with that (though would welcome suggestions for other approaches).

Authentication is managed by namespaced application controllers – here’s an example for version 2, where I’m using headers:

class Api::V2::ApplicationController < ApplicationController
  skip_before_filter :verify_authenticity_token
  before_filter :check_api_params

  expose(:app) { App.find_by_identifier identifier }

  private

  def check_api_params
    # ensure the response returns with the same header value
    headers['X-Flying-Sphinx-Token'] = request.headers['X-Flying-Sphinx-Token']
    render_json_with_code 403 unless app && app.api_key == api_key
  end

  def api_token
    request.headers['X-Flying-Sphinx-Token']
  end

  def identifier
    api_token && api_token.split(':').first
  end

  def api_key
    api_token && api_token.split(':').last
  end
end

Authentication, in case it’s not clear, is done by a header named X-Flying-Sphinx-Token with a value of the account’s identifier and api_key concatenated together, separated by a colon.

(If you’re not familiar with the expose method, that’s from the excellent decent_exposure gem.)

So where does that leave us? Well, we have an elegantly namespaced API, and both versions and authentication is managed in headers instead of paths and parameters. I also made sure version 2 responses all return JSON. Josh is happy and all versions of the flying-sphinx gem are happy.

The one caveat with all of this? While it works for me, and it suits Flying Sphinx, it’s not the One True Way for API development. We had a great discussion at the most recent Rails Camp up at Lake Ainsworth about different approaches – at the end of the day, it really comes down to the complexity of your API and who it will be used by.

02 Jan 2008

Postie - The Gem

An addition to auspostie.com, prompted by Dr Nic’s suggestion in the comments of the earlier blog post – the postie gem. It allows easy parsing of Postie search results, and also provides a command-line tool for searching.

By suburb:

postie Brunswick

By postcode:

postie 3070

To install:

sudo gem install postie

To use within your own ruby code:

require 'postie'

Postie::Locality.find("Melbourne")

Again, extremely simple, but just makes access to the data that little bit easier.

Now, what would be really cool is a Quicksilver plugin that queries the API. Any volunteers to code that up?

30 Dec 2007

Postcode API

A couple of weeks ago I quickly coded a basic webservice – using Merb – for Australian Postcode data. Just got hosting for it sorted out last night, so now I can blog about it.

Postie

The emphasis is on simple – you can search by postcode or suburb, and get the data back either as a HTML page, XML, or JSON. That’s pretty much it. The request urls aren’t complicated, either:

Suburb requests use partial matching, so you don’t need the full suburb name. If you want the JSON returned with the MIME type of application/json, use the .json extension.

I’ve no plans at this point on using this, but perhaps it’s useful for someone out there – if so, would love to hear about it.

19 Sep 2007

Link: Free data sharing is here to stay | Technology | Guardian Unlimited

"Like a bottled water company, we compete with free by supplying a superior service, not by eliminating the competition."

29 Mar 2007

Link: Ask the Wizard: Creating Competitors

"The best reason to always build out API's for your product is that it makes it easier for the rest of the world to extend your product or service rather than start competitors."

24 Feb 2007

Link: GeoKit for Rails: home

03 Dec 2006

Link: 24 ways: Flickr Photos On Demand with getFlickr

12 Oct 2006

Link: WorldChanging: Tools, Models and Ideas for Building a Bright Green Future: Google Transit Hits Five New Cities and releases API

20 Sep 2006

Link: ActiveMerchant

12 Sep 2006

Link: Net::XMPP library for Ruby

12 Sep 2006

Link: XMPP4R: XMPP/Jabber Library for Ruby

24 Aug 2006

Link: Timing out and retrying calls to third parties

See comments for other suggestions

13 Apr 2006

Link: gotAPI.com :: API lookup service

helpful reference to a stack of apis

RssSubscribe to the RSS feed

About Freelancing Gods

Freelancing Gods is written by , who works on the web as a web developer in Melbourne, Australia, specialising in Ruby on Rails.

In case you're wondering what the likely content here will be about (besides code), keep in mind that Pat is passionate about the internet, music, politics, comedy, bringing people together, and making a difference. And pancakes.

His ego isn't as bad as you may think. Honest.

Here's more than you ever wanted to know.

Ruby on Rails Projects

Other Sites

Creative Commons Logo All original content on this site is available through a Creative Commons by-nc-sa licence.