Versioning APIs: Ruby on Rails Design Patterns

URL-based Versioning and Header-based Versioning API versioning

Patrick Karsh
NYC Ruby On Rails
5 min readAug 1, 2023

--

Building and maintaining APIs in Ruby on Rails requires careful consideration of versioning to ensure seamless updates and smooth interactions with client applications. This article will delve into two common versioning approaches: URL-based and header-based versioning. We will explore their implementation, benefits, and code examples to help you make informed decisions when versioning your Rails APIs.

URL-based Versioning

URL-based versioning involves specifying the API version as part of the URL path. This approach makes the version explicit and easy to understand for both developers and clients.

Step 1: Create API Version Modules

Start by creating separate modules for each API version within the controllers folder. This segregation keeps the code organized and allows for clear version-specific implementations.

# app/controllers/api/v1/users_controller.rb
module Api
module V1
class UsersController < ApplicationController
# Version 1 controller actions
end
end
end

# app/controllers/api/v2/users_controller.rb
module Api
module V2
class UsersController < ApplicationController
# Version 2 controller actions
end
end
end

Step 2: Configure Routes

Next, define routes for each API version using the scope method in the config/routes.rb file.

Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :users
end

namespace :v2 do
resources :users
end
end
end

Step 3: Implement Controllers

Now, you can implement the controller actions specific to each version. This allows for separate business logic and functionality for different API versions.

# app/controllers/api/v1/users_controller.rb
module Api
module V1
class UsersController < ApplicationController
def index
# Version 1 index action
end

def show
# Version 1 show action
end

# Other version 1 actions
end
end
end

# app/controllers/api/v2/users_controller.rb
module Api
module V2
class UsersController < ApplicationController
def index
# Version 2 index action
end

def show
# Version 2 show action
end

# Other version 2 actions
end
end
end

Header-based Versioning

Header-based versioning involves specifying the version in the request headers, commonly using the Accept header. This approach allows for cleaner URLs and is more suitable for complex API designs.

Step 1: Create API Controllers

Unlike URL-based versioning, header-based versioning uses a single controller for all versions. Create a regular controller without version-specific modules.

# app/controllers/api/users_controller.rb
class Api::UsersController < ApplicationController
def index
# Determine version from the request headers and call the appropriate method
if request.headers['Accept'] =~ /application\/vnd\.example\.v1\+json/
version_1_index
elsif request.headers['Accept'] =~ /application\/vnd\.example\.v2\+json/
version_2_index
else
# Return appropriate error response for unsupported version
end
end

# Other shared actions
end

Step 2: Define Version-specific Methods

In the same controller, define version-specific methods that correspond to different API versions.

# app/controllers/api/users_controller.rb
class Api::UsersController < ApplicationController
def version_1_index
# Version 1 index action
end

def version_2_index
# Version 2 index action
end

# Other shared actions
end

Step 3: Route All Versions to the Same Controller

In the config/routes.rb file, set up routes to direct all API versions to the same controller.

Rails.application.routes.draw do
namespace :api do
resources :users
end
end

URL-based and header-based versioning each have their own set of advantages and disadvantages. Let’s explore them to help you make an informed decision on which approach to choose for versioning your Rails APIs.

The Advantages and Disadvantages of URL-based Versioning and Header-based Versioning

Advantages URL-based Versioning

Explicit Versioning: The version is clearly visible in the URL, making it easy for developers and clients to understand and identify the version being used.

Caching: Since different versions have separate URLs, caching can be utilized efficiently. Clients can cache responses for a specific version without interfering with other versions.

Simplified Routing: Routing is straightforward and direct, as each version has its own set of routes, making it easy to manage and navigate.

Client Control: Clients can easily choose the desired version by specifying it in the URL, giving them more control over the API version they interact with.

Disadvantages URL-based Versioning:

Cluttered Codebase: As versions are organized into separate controller modules, this may lead to a more complex codebase, especially if there are numerous versions to maintain.

Routing Complexity: Over time, as new versions are introduced, the routes file can become crowded and harder to manage.

Potential for Link Rot: If old URLs are not properly redirected or maintained, there’s a risk of link rot when clients use outdated URLs.

Advantages of Header-based Versioning

Cleaner URLs: By using a single controller and avoiding version numbers in the URL, the API endpoints look cleaner and more user-friendly.

Flexibility: The API can evolve without changing the URL structure. This allows for more flexible versioning and prevents clients from being tightly coupled to specific versions.

Reduced Code Duplication: Version-specific logic can be organized within separate methods inside a single controller, reducing the need for duplicated code.

Ease of Adoption: It’s easier to integrate new versions into the API without introducing changes to the routing configuration.

Disadvantages of Header-based Versioning

Potentially Ambiguous: Clients may need to carefully set the Accept header with the correct version information, and there's a potential for ambiguity if the header is not accurately specified.

Caching Challenges: Caching becomes more complex, as different versions may be served from the same URLs, potentially leading to unexpected caching behavior.

Testing Complexity: Testing versioned endpoints may require additional effort to simulate different Accept headers in test scenarios.

Dependency on Headers: Some clients may not handle headers correctly, leading to potential version negotiation issues.

Choosing the right versioning approach depends on the specific needs of your project. If your API is relatively small and only has a few versions, URL-based versioning might be a straightforward and explicit option. On the other hand, if your API is more complex and requires more flexibility in versioning, header-based versioning could be a cleaner and more scalable choice. Consider your project’s requirements, your team’s expertise, and the preferences of your API consumers when making this decision.

Conclusion

Versioning Rails APIs is crucial to maintain backward compatibility and facilitate smooth updates. URL-based versioning allows for clear separation of versions through different controller modules, while header-based versioning provides cleaner URLs and is suitable for complex API designs. Choose the approach that aligns with your project requirements and scale your API confidently, knowing that different versions can evolve independently while ensuring a seamless experience for your clients. Happy coding!

--

--

Patrick Karsh
NYC Ruby On Rails

NYC-based Ruby on Rails and Javascript Engineer leveraging AI to explore Engineering. https://linktr.ee/patrickkarsh