A Deeper Dive into API Versioning

Eun Yi
The Startup
Published in
5 min readJul 3, 2019

An interesting topic came up during an interview where an interviewer asked about my approach to API versioning. I didn’t know much about API versioning so I decided to dig a little deeper into this particular concept in programming and explore some of the best practices.

Pt 1: How to version a Rails API in the request URL

Versioning: What is it? Why is it important? When to version?

When you’re building an API for a project, it is highly recommended that you version your API (which is a form of contract between you, the API provider, and the consumer). Why? Because when the API changes, either because you released new features or changed the endpoints, any application using the API would run the risk of being broken until they updated their code. This can cause some serious issues with your API clients. This is similar to the problem we see with web scraping when the website changes.

So if you want your API to go public, you definitely want to establish some sort of contract with your clients. With a contract (or API version) in place, you can guarantee that anyone using the current version can keep using it and upgrade to the new version when they are able to.

Just to give you an example, think of software update notifications you receive on your phone, computer, tablet, etc. When you get a notification to upgrade to the latest release, you don’t always do it right away. Instead you wait until you have enough battery power or wifi to do so.

According to this particular source, versioning the API may either be based on the request URL, or based on the request headers.

In this blog, we will explore how Rails routes can be applied to API versioning and look at two most common versioning methods:

  1. API version in request URL (pt.1)
  2. API version in request headers (pt.2 coming soon)

The first option to do the versioning, as mentioned, is to store the version as part of the URL. So we’ll need to start in the routes.rb file in the config folder.

First, we’ll namespace the Api then we add another namespace called v1 (for version 1) to specify the version that we want to make available to public.

#config/routes.rbRails.application.routes.draw do namespace :api do
namespace :v1 do
resources :todos
end
end
end

What this means is that any routes in the namespace will be prefixed with an /api/v1/ path and any controllers will be found under the Api module and V1 (more explanation on this later).

Then, we want to create an api folder and a v1 folder to match the route path and place our controller file inside v1. The folder structure should look something like this:

NoteApp
|_app
|_models
|_views
|_controllers
|_api
|_v1
|_todos_controller.rb

After namespacing the controller:

#app/controllers/api/v1/todos_controller.rbmodule Api::V1
class TodosController < ApplicationController

def index
@todos = Todo.all
render json: @todos
end
.... end
end

Now when we visit the following URL(http://localhost:3000/api/v1/todos) in the browser or via Postman, we’ll see all the todos returned in JSON format:

Postman

But let say we want to make changes to our Todo attribute and rename it.

We can do so by running the following command:

rails g migration change_todos_title--no-test-framework

Then, in our migration file, simply add in the rename_column method. Here we’re changing title to name:

class ChangeTodosTitle < ActiveRecord::Migration[5.2]  def change
rename_column :todos, :title, :name
end
end

Then, in our controller file, we’re going to create a subclass that will inherit from the actual Todo model and any references to the Todo class will now use the subclass. We’ll use super to get the original JSON hash and merge on any other attributes such as adding on the title attribute that will become the name attribute.

module Api::V1
class TodosController < ApplicationController

class Todo < ::Todo
def as_json(options={})
super.merge(title: name)
end
end
.... end
end

When we reload the page, we’ll see both the title attribute and the name attribute in JSON format. This way, it’ll ease the transition for a client who still has the old api (v1):

Postman

Finally, to release v2 (version 2)of the API, we will first need to create a v2 folder by copying over v1 folder by running this command:

cp -R app/controllers/api/v1 app/controllers/api/v2

Our directory tree should like this:

|_controllers
|_api
|_v1
|_todos_controller.rb
|_v2
|_todos_controller.rb

Inside v2 folder, we want to change the module to V2 in our controller and refactor any code (in our case, we’ve removed the subclass):

#app/controllers/api/v2/todos_controller.rbmodule Api::V2
class TodosController < ApplicationController

def index
@todos = Todo.all
render json: @todos
end
.... end
end

We also need to add new routes for v2:

Rails.application.routes.draw donamespace :api do
namespace :v1 do
resources :todos
end
end
namespace :api do
namespace :v2 do
resources :todos
end
end
end

Now when we restart the server and visit http://localhost:3000/api/v2/todos, we no longer see the title attribute.

Postman

By versioning the API, we can guarantee that anyone using v1 can continue using it and upgrade to the new version when they are ready to update their code. So we leave the old endpoints in place (e.g. http://localhost:3000/api/v1/todos) and roll out a new one under a new version namespace (e.g. http://localhost:3000/api/v2/todos). This way everyone on v1 can keep working without worrying about breaking their application.

There are other API versioning methods available, but I discovered that the request URI option is the most common way and also the easiest. However, I’m not quite sure whether it is the best approach but it sure is better than not having anything at all. In case for future expansion, it would definitely save you a headache.

You can see what the code looks like: https://github.com/eyi1/Todo-Api

Pt 2 coming soon.

Sources:

--

--