Skip to content


Subversion checkout URL

You can clone with
Download ZIP
Fetching contributors…
Cannot retrieve contributors at this time
188 lines (157 sloc) 11.8 KB

api-versions Build Status

api-versions is a Gem to help you manage your Rails API endpoints.

api-versions is very lightweight. It adds a generator and only one method to the Rails route mapper.

It helps you in three ways:
  • Provides a DSL for versioning your API in your routes file, favoring client headers vs changing the resource URLs.
  • Provides methods to cache and retrieve resources in your routes file to keep it from getting cluttered
  • Provides a generator to bump your API controllers to the next version, while inheriting the previous version.

See below for more details on each of these topics

Assumptions api-versions makes:

  • You want the client to use headers to specify the API version instead of changing the URL. (Accept header of application/vnd.myvendor+json;version=1 for example)
  • You specify your API version in whole integers. v1, v2, v3, etc. If you need semantic versioning for an API you're likely making too many backwards incompatible changes. API versions should not change all that often.
  • Your API controllers will live under the api/v{n}/ directory. For example app/controllers/api/v1/authorizations_controller.rb.


In your Gemfile:

gem "api-versions", "~> 0.1.0"

Versions are specified by header, not by URL

A lot of APIs are versioned by changing the URL. for example. But is some_resource different from version 1 to version 2? It is likely the same resource, it is simply the interface that is changing. api-versions prefers the URLs stay the same. need not ever change (so long as the resource exists). The client specifies how it wants to interface with this resource with the Accept header. So if the client wants version 2 of the API, the Accept header might look like this: application/vnd.myvendor+json;version=2. A complete example is below.


api-versions provides a (very) lightweight DSL for your routes file. Everything having to do with your routes API lives in the api block. This DSL helps you version your API as well as providing a caching mechanism to prevent the need of copy/pasting the same resources into new versions of the API.

For example:

In your routes.rb file:

  # You can leave default_version out, but if you do the first version used will become the default
  api vendor_string: "myvendor", default_version: 1 do
    version 1 do
      cache as: 'v1' do
        resources :authorizations

    version 2 do
      inherit from: 'v1'

rake routes outputs:

api_authorizations      GET    /api/authorizations(.:format)          api/v1/authorizations#index
                        POST   /api/authorizations(.:format)          api/v1/authorizations#create
new_api_authorization   GET    /api/authorizations/new(.:format)      api/v1/authorizations#new
edit_api_authorization  GET    /api/authorizations/:id/edit(.:format) api/v1/authorizations#edit
 api_authorization      GET    /api/authorizations/:id(.:format)      api/v1/authorizations#show
                        PUT    /api/authorizations/:id(.:format)      api/v1/authorizations#update
                        DELETE /api/authorizations/:id(.:format)      api/v1/authorizations#destroy
                        GET    /api/authorizations(.:format)          api/v2/authorizations#index
                        POST   /api/authorizations(.:format)          api/v2/authorizations#create
                        GET    /api/authorizations/new(.:format)      api/v2/authorizations#new
                        GET    /api/authorizations/:id/edit(.:format) api/v2/authorizations#edit
                        GET    /api/authorizations/:id(.:format)      api/v2/authorizations#show
                        PUT    /api/authorizations/:id(.:format)      api/v2/authorizations#update
                        DELETE /api/authorizations/:id(.:format)      api/v2/authorizations#destroy

Then the client simply sets the Accept header application/vnd.myvendor+json;version=1. If no version is specified, the default version you set will be assumed. You'll of course still need to copy all of your controllers over, even if they haven't changed from version to version. At least you'll remove a bit of the mess in your routes file.

A more complicated example:

  api vendor_string: "myvendor", default_version: 1 do
    version 1 do
      cache as: 'v1' do
        resources :authorizations, only: :create
        resources :foo
        resources :bar

    version 2 do
      cache as: 'v2' do
        inherit from: 'v1'
        resources :my_new_resource

    # V3 has everything in V2, and everything in V1 as well by virtue of V1 being cached in V2.
    version 3 do
      inherit from: 'v2'

And finally rake routes outputs:

api_authorizations        POST   /api/authorizations(.:format)           api/v1/authorizations#create
            api_foo_index GET    /api/foo(.:format)                      api/v1/foo#index
                          POST   /api/foo(.:format)                      api/v1/foo#create
              new_api_foo GET    /api/foo/new(.:format)                  api/v1/foo#new
             edit_api_foo GET    /api/foo/:id/edit(.:format)             api/v1/foo#edit
                  api_foo GET    /api/foo/:id(.:format)                  api/v1/foo#show
                          PUT    /api/foo/:id(.:format)                  api/v1/foo#update
                          DELETE /api/foo/:id(.:format)                  api/v1/foo#destroy
            api_bar_index GET    /api/bar(.:format)                      api/v1/bar#index
                          POST   /api/bar(.:format)                      api/v1/bar#create
              new_api_bar GET    /api/bar/new(.:format)                  api/v1/bar#new
             edit_api_bar GET    /api/bar/:id/edit(.:format)             api/v1/bar#edit
                  api_bar GET    /api/bar/:id(.:format)                  api/v1/bar#show
                          PUT    /api/bar/:id(.:format)                  api/v1/bar#update
                          DELETE /api/bar/:id(.:format)                  api/v1/bar#destroy
                          POST   /api/authorizations(.:format)           api/v2/authorizations#create
                          GET    /api/foo(.:format)                      api/v2/foo#index
                          POST   /api/foo(.:format)                      api/v2/foo#create
                          GET    /api/foo/new(.:format)                  api/v2/foo#new
                          GET    /api/foo/:id/edit(.:format)             api/v2/foo#edit
                          GET    /api/foo/:id(.:format)                  api/v2/foo#show
                          PUT    /api/foo/:id(.:format)                  api/v2/foo#update
                          DELETE /api/foo/:id(.:format)                  api/v2/foo#destroy
                          GET    /api/bar(.:format)                      api/v2/bar#index
                          POST   /api/bar(.:format)                      api/v2/bar#create
                          GET    /api/bar/new(.:format)                  api/v2/bar#new
                          GET    /api/bar/:id/edit(.:format)             api/v2/bar#edit
                          GET    /api/bar/:id(.:format)                  api/v2/bar#show
                          PUT    /api/bar/:id(.:format)                  api/v2/bar#update
                          DELETE /api/bar/:id(.:format)                  api/v2/bar#destroy
api_my_new_resource_index GET    /api/my_new_resource(.:format)          api/v2/my_new_resource#index
                          POST   /api/my_new_resource(.:format)          api/v2/my_new_resource#create
  new_api_my_new_resource GET    /api/my_new_resource/new(.:format)      api/v2/my_new_resource#new
 edit_api_my_new_resource GET    /api/my_new_resource/:id/edit(.:format) api/v2/my_new_resource#edit
      api_my_new_resource GET    /api/my_new_resource/:id(.:format)      api/v2/my_new_resource#show
                          PUT    /api/my_new_resource/:id(.:format)      api/v2/my_new_resource#update
                          DELETE /api/my_new_resource/:id(.:format)      api/v2/my_new_resource#destroy
                          POST   /api/authorizations(.:format)           api/v3/authorizations#create
                          GET    /api/foo(.:format)                      api/v3/foo#index
                          POST   /api/foo(.:format)                      api/v3/foo#create
                          GET    /api/foo/new(.:format)                  api/v3/foo#new
                          GET    /api/foo/:id/edit(.:format)             api/v3/foo#edit
                          GET    /api/foo/:id(.:format)                  api/v3/foo#show
                          PUT    /api/foo/:id(.:format)                  api/v3/foo#update
                          DELETE /api/foo/:id(.:format)                  api/v3/foo#destroy
                          GET    /api/bar(.:format)                      api/v3/bar#index
                          POST   /api/bar(.:format)                      api/v3/bar#create
                          GET    /api/bar/new(.:format)                  api/v3/bar#new
                          GET    /api/bar/:id/edit(.:format)             api/v3/bar#edit
                          GET    /api/bar/:id(.:format)                  api/v3/bar#show
                          PUT    /api/bar/:id(.:format)                  api/v3/bar#update
                          DELETE /api/bar/:id(.:format)                  api/v3/bar#destroy
                          GET    /api/my_new_resource(.:format)          api/v3/my_new_resource#index
                          POST   /api/my_new_resource(.:format)          api/v3/my_new_resource#create
                          GET    /api/my_new_resource/new(.:format)      api/v3/my_new_resource#new
                          GET    /api/my_new_resource/:id/edit(.:format) api/v3/my_new_resource#edit
                          GET    /api/my_new_resource/:id(.:format)      api/v3/my_new_resource#show
                          PUT    /api/my_new_resource/:id(.:format)      api/v3/my_new_resource#update
                          DELETE /api/my_new_resource/:id(.:format)      api/v3/my_new_resource#destroy


The api-versions gem provides a Rails generator called api_versions:bump. This generator will go through all of your API controllers and find the highest version number and bump all controllers with it up to the next in sequence.

If for example you have a controller api/v1/authorizations_controller.rb it will create api/v2/authorizations_controller.rb and inside:

class Api::V2::AuthorizationsController < Api::V1::AuthorizationsController

So instead of copying your prior version controllers over to the new ones and duplicating all the code in them, you can redefine specific methods, or start from scratch by removing the inheritance.

A word on Rails responders

If you use responds_to and responds_with you'll run into a bit of a problem. Rails doesn't understand the Accept header so you'll get a 406 Unacceptable if you use respond_to. To get around this I suggest creating a base API controller and including ApiVersions::SimplifyFormat, which will set the format to the one specified with the + symbol in the Accept header.

class Api::V1::BaseController < ActionController::Base
  include ApiVersions::SimplifyFormat

This will set request.format accordingly.

Jump to Line
Something went wrong with that request. Please try again.