Logo Devoh

Simple API Versioning in Rails

During my tenure at NECO, I had the opportunity to develop a web service to provide access to the ticket catalog and ordering system. While I had built web service endpoints before, they weren't much more than XML and JSON views into the database. This was the first time I had been tasked with building a full-fledged web service.

I knew going into it that I wanted to allow for API versioning. I didn't know much about who would be using the service, or what use cases may arise after it was launched, and versioning seemed like the best mechanism to provide our partners with a migration path should big changes need to be made. I remember trying to find a good solution to handle that, but not finding much on the topic at the time. Ultimately, I settled on a solution that was already baked right into Rails: namespaces.

What I ended up with was a top-level namespace for each version of the API. At the URL level, this meant the first element of the path would be the version, e.g. /v1 or /v2. Here's what the routes might look like, showing a change between versions:

map.namespace(:v1) do |v1|
  v1.resources :categories
  v1.resources :products

map.namespace(:v2) do |v2|
  v2.resources :categories, :has_many => :products

With this arrangement, not only are the routes namespaced, but so are the controllers. Since Rails already espouses the concept of thin controllers, I took the view that, in addition to the routes, the controllers are the interface to the outside world.

If breaking changes need to be made to the API, adding a new version is just a matter of duplicating the controllers and adding a new namespace to the routes file. From there, any changes made will be isolated to the new namespace, keeping the original version intact. Then, once users have migrated to the new version, removing the old version is just a matter of deleting its namespaced controller directory and corresponding routes.

In short, mapping API versions into segregated, thin controllers provides the smallest surface area for the public interface that still provides all the flexibility needed to maintain compatibility across multiple versions. And—bonus—there's no plugin necessary; it's included for free in every Rails app.