Introduction
As APIs evolve, you need to make breaking changes without disrupting existing clients. API versioning lets you run multiple versions simultaneously, giving clients time to migrate.
Key Concepts
- URL Versioning: Version in the URL path (
/api/v1/products). Most common and explicit. - Header Versioning: Version in a custom header (
Api-Version: 2). Cleaner URLs but less discoverable. - Namespace Versioning: Using Rails modules to organize versioned controllers.
- Deprecation: Marking old versions as deprecated before removal.
Real World Context
GitHub uses URL versioning, Stripe uses API version dates in headers, and Twilio uses URL versioning. URL versioning is the most common because it's explicit, easy to test, and works with all HTTP clients without special header configuration.
Deep Dive
URL Versioning with Namespaces
ruby# config/routes.rb Rails.application.routes.draw do namespace :api do namespace :v1 do resources :products resources :users end namespace :v2 do resources :products resources :users end end end
Controllers live in separate modules:
ruby# app/controllers/api/v1/products_controller.rb module Api::V1 class ProductsController < ApplicationController def index render json: Product.all.as_json(only: [:id, :name, :price]) end end end # app/controllers/api/v2/products_controller.rb module Api::V2 class ProductsController < ApplicationController def index render json: ProductSerializer.new(Product.all).serializable_hash end end end
V1 uses simple as_json, V2 uses a serializer with a richer format. Both run simultaneously.
Sharing Logic Between Versions
ruby# app/controllers/concerns/product_actions.rb module ProductActions extend ActiveSupport::Concern def set_product @product = Product.find(params[:id]) end def product_params params.require(:product).permit(:name, :price, :description) end end # Both versions include shared logic module Api::V1 class ProductsController < ApplicationController include ProductActions end end
Concerns prevent code duplication between versions.
Common Pitfalls
- Not versioning from day one — Adding versioning later requires restructuring all controllers and routes.
- Too many active versions — Maintain at most 2-3 versions. Deprecate and remove old ones.
Best Practices
- Start with
/api/v1/from the beginning — Even if you think you won't need v2. - Share logic via concerns — Don't duplicate business logic across versions.
Summary
- URL versioning (
/api/v1/) is the most common and explicit strategy. - Rails namespaces map cleanly to versioned controller modules.
- Share common logic between versions using concerns.
- Deprecate old versions before removing them.
- Start versioning from day one.
Code Examples
ruby
# config/routes.rb — Multi-version API routing
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :products
end
namespace :v2 do
resources :products
end
end
end
# /api/v1/products → Api::V1::ProductsController
# /api/v2/products → Api::V2::ProductsController