Versioning APIs is a critical part of building web applications, as it allows you to make changes that may otherwise break existing API users. Changing the contract between the API and the clients that depend on it is dangerous, and versioning endpoints adds flexibility and safety. Versioning is implemented in many ways - You can version with subdomains, query parameters, URL schemas, headers, and more!

Still, developers looking to introduce a new version of an API that wasn't originally versioned may find themselves in a pickle. Clients may be calling an endpoint that takes the form base_url/all_users, so replacing it with something like base_url/v1/users presents an obvious problem. In this article, we'll explore how to version an API built with Ruby on Rails and walk through options for versioning an existing Rails API.

Why versioning is important

Change is one of the only guarantees in programming. APIs often change over time to fit business needs, fix bugs, introduce improvements, or simply change functionality. Many changes can be made without affecting the contract between the API and the client, but some changes unavoidably introduce something the caller of the API does not expect.

API updates that change behavior in a way that may surprise existing clients of the API are called "breaking changes." If you're renaming an endpoint, callers of the API using the old endpoint name will immediately experience HTTP 404 errors when your update is deployed. Introducing new required parameters, changing HTTP verbs, or changing the API response can all introduce breaking changes.

If an API is versioned, breaking changes can be rolled out to a new version of the API, and clients can call the new version when they've had a chance to update their code to adapt to the breaking changes. An endpoint previously named base_url/v1/all_users can be renamed in a v2 version, and clients can call the new base_url/v2/users endpoint without interrupting the application's health. Still, versioning an API comes at the cost of increased maintenance of multiple sets of endpoints with similar logic. You can read more about API versioning in this great resource from Postman.

Namespacing in Rails routes

Fortunately for Rails developers, versioning is an established problem with an established solution! While there are many ways to version an API, it is common to use a version number directly in the route of an endpoint. As discussed above, a versioned route would look something like base_url/v1/notes and base_url/v2/notes. Sometimes, the URL is the same outside of the version, but it can differ!

Namespacing routes

To introduce versioning into an API, you will first need to add namespaces for your versions in the routes file of your rails app. You can do this to an existing API! Let's take a look at an example config/routes.rb:

root to: 'home#index'

resource :users
resource :courses
resource :assignments

If you wanted to introduce a single v1 to these resources, you should wrap them in a namespace:

root to: 'home#index'

namespace :v1 do
  resource :users
  resource :courses
  resource :assignments
end

This is useful if you know that you only have one version for now, but adding a second version is simple. You can introduce a set of v2 endpoints just as easily:

root to: 'home#index'

namespace :v1 do
  resource :users
  resource :courses
  resource :assignments
end

namespace :v2 do
  resource :users
  resource :courses
  resource :assignments
end

This introduces some duplication to the routes file, which is a code smell that we can easily remedy. Introducing concerns into the routes file can fix this. The above config/routes.rb can be changed to the following with the same result:

root to: 'home#index'

concern :api do
  resource :users
  resource :courses
  resource :assignments
end

namespace :v1 do
  concerns :api
end

namespace :v2 do
  concerns :api
end

Versioning Rails controllers

These namespaced routes will direct traffic to the appropriate namespaced controllers, so you'll need to change your controller names to match. To begin, create the appropriate directories under app/controllers. If you've introduced a v1 namespace, then create a directory, app/controllers/v1/. Next, move any controllers that will handle traffic for your v1 namespace under this new directory.

Lastly, you'll need to rename the controllers themselves to match the namespace. If the Courses controller class is defined as the following:

class CoursesController < ApplicationController

Then, to move it to the v1 namespace, redefine it as

class V1::CoursesController < ApplicationController

Repeat this process for any controllers in the v1 namespace, and your application will now have fully functional versioned endpoints!

Handling unversioned requests for backwards compatibility

Introducing versions to a production API that doesn't already have them is potentially a breaking change itself, which is ironic! Existing clients of the API would be calling routes that no longer exist until they are updated to provide a version in the URL.

Fortunately, we can remedy this inside our Rails API. We can provide a default route so unversioned requests are routed to a namespace of our choice, like v1.

You can do this in routes.rb with a scope block that references your api concern. The Rails Guides explain this well in the routing section. Add the scope block to the bottom of your config/routes.rb file like this:

root to: 'home#index'

concern :api do
  resource :users
  resource :courses
  resource :assignments
end

namespace :v1 do
  concerns :api
end

namespace :v2 do
  concerns :api
end

scope module: 'v1' do
  concerns :api_base
end

Conclusion

From leveraging namespaces in routing to organizing controllers within version-specific directories, these essential components of an API allow for ease of change. They provide a structured approach to evolve APIs while safeguarding against potential breaking changes. Whether you're a seasoned developer or new to the Rails ecosystem, the insights shared here equip you with the knowledge to manage API versions effectively, ensuring your applications continue to support clients reliably even through major changes.

Get the Honeybadger newsletter

Each month we share news, best practices, and stories from the DevOps & monitoring community—exclusively for developers like you.
    author photo
    Jeffery Morhous

    Jeff is a Software Engineer working in healtcare technology using Ruby on Rails, React, and plenty more tools. He loves making things that make life more interesting and learning as much he can on the way. In his spare time, he loves to play guitar, hike, and tinker with cars.

    More articles by Jeffery Morhous
    An advertisement for Honeybadger that reads 'Turn your logs into events.'

    "Splunk-like querying without having to sell my kidneys? nice"

    That’s a direct quote from someone who just saw Honeybadger Insights. It’s a bit like Papertrail or DataDog—but with just the good parts and a reasonable price tag.

    Best of all, Insights logging is available on our free tier as part of a comprehensive monitoring suite including error tracking, uptime monitoring, status pages, and more.

    Start logging for FREE
    Simple 5-minute setup — No credit card required