— Apr 16, 2014
Originally posted on the Big Nerd Ranch Blog.
When you create an application programming interface (API), you’re establishing a contract with everyone who uses it. This too is true for web service APIs. As soon as someone begins using an API, changes require coordination between all clients in order to prevent breakage, costing precious time and money.
Rails API versioning to the rescue: In order to allow breaking changes to an interface, we can version it so that clients may specify exactly what representation they expect for their requests. Then they are able to decide for themselves when it is time- and cost-effective to upgrade their dependency.
Note: Each heading in this walkthrough will have one or more accompanying commits. You can work through it yourself or follow along on Github.
As a baseline for this post, we’ll consider a very simple, contrived API. This API has only one resource:
/articles. You can grab a copy of the example by cloning it from Github and setting up as follows.
Clone the repo:
Checkout the repo in its “initial” state, before versions are implemented:
Install dependencies, watch specs pass:
Once you’ve got the project set up, let’s run the local server and see what the article response looks like.
Run the local rails server:
Make a request for the articles’ JSON representation:
At this point, our API has a single, unversioned articles resource. As a first step, we’ll introduce versioning as an internet media type parameter.
Namespacing is a great way to keep code organized. We’ll wrap our existing controller in a
V1 module to establish our “version 1.”
app/controllers/v1/articles_controller.rb and wrap the class in a module.
Since we don’t want to affect our URI structure for the resource, we can use the
:module scope to namespace the controller and not the URI.
Next we need to tell Rails how to route requests for different versioned representations. We can do this effectively by using a route constraint that checks for a version specified in the request’s
We can use this constraint to route requests based on the version specified in the request’s
With this change, we now must specify the desired version in the request’s headers to get the desired response. If your development server is still running at this point, it will probably need to be restarted.
Now that we have namespaces for our versioned controllers and constraints for routing, we can introduce a version 2 articles representation. Version 2 will wrap the response in a root node. This representation is not backwards compatible with the version 1 representation, thus requiring a new versioned representation.
We can request the version 2 representation as well as version 1:llows.
I was first tuned to this idea by a post from Steve Klabnik about REST and HTTP. Later I dug a little deeper and found a long answer on StackOverflow that goes into more detail. The prevaling sentiment is:
resource URIs that API users depend on should be permalinks
This cannot be true if the version is included in the URI, which changes over time. Using Klabnik’s suggestion, we push this knowledge into the request headers and keep URIs permanent for all future representations of our resources.
The above example deals only in the
application/json media type. The idea of “versioning” doesn’t apply very well to the generic “json” type. That is, it doesn’t make good sense to say, “Here you’re seeing version 1 json, and over here we have version 2 json.” For that reaso,n it may be desirable to create a custom vendor media type to represent our app’s responses.
First we register a new type with Rails and give it a name.
Now we can adjust our resource to respond to our custom type.
With this change we can now make requests of our custom type.
You may have noticed that I stopped using format suffixes for requests early in the post (e.g.,
/articles.json). This was done intentionally so that we could arrive at custom media types. It is, however, somewhat common to include such suffixes for requested resources. Unfortunately, Rails becomes confused into thinking that we’re requesting pure JSON rather than our “articles flavored JSON.” We can address this by responding to both formats.
Now we can make request including the suffix.
There is one caveat. The content type of the response will be
application/json rather than our custom type. I provide a little more information in my commit notes.
Versioning code is a Good Thing™. It allows us to continue to extend our APIs without breaking compatibility for existing users. Introducing API versions after a release may be a little painful, but it’s doable.comments powered by Disqus