To implement API versioning in Rails, not using URL namespaces but custom MIME types, there are a few different approches.
The Tribesports way
Recently, I’ve seen a blog post about the Tribesports API. They chose to add a new MIME type to the application and use the rendering features of Rails.
Maybe I’ve not seen all the constraints they might have, and that lead to this choice, but from my point of view, 2 things are bothering me :
- they had to change too much things in the rendering process
- they created the
api_v1content type but in fact it’s plain JSON
What if they wanted to render JSON or XML but for the version 1 of their API ?
With a simple protected method in the ApplicationController, it’s possible to inspect the
Accept HTTP header, and extract an API version number, while letting Rails decide what is the real content type to use.
class ApplicationController < ActionController::Base respond_to :html, :xml, :json protected def api_version default_version = '1' pattern = /application\/vnd\.com\.example\.api\.v([\d\.]+)\+.*/ request.env['HTTP_ACCEPT'][pattern, 1] || default_version end end
In this example, by default all the action method of all controllers will respond to HTML, XML and JSON, and Rails is probably using the default HTML if nothing is specified. I can even implement a rendering for another format in the respond_to block of a action method, like format.js for Ajax requests, …
We now have a method, accessible from all controller methods (including actions) to get the desired API version nummber. If an "application/vnd"-style
Accept header is found and if a version number can be extracted, then it is used. There is a fallback to the default version.
With this, you can have any test you want at the controller level on the API version, without messing with the content type.
About the default version number, some prefer the lastest (and allow clients to set a specific version), and some prefer the first version. I prefer the "latest by default" way.
NB : I’m quite sure I’ve not made this one up, but I honestly can’t remember where I’ve read this from.