Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :posts do
resources :comments, only: [:index, :create]
end
resources :users, only: [:show, :update]
end
namespace :v2 do
resources :posts do
resources :comments
member do
post :publish
post :archive
end
end
resources :users
resources :profiles
end
# Default to latest version
root to: redirect('/api/v2')
end
end
module Api
module V1
class PostsController < ApplicationController
def index
posts = Post.published.includes(:author).page(params[:page])
# Add deprecation warning
response.headers['X-API-Deprecation'] = 'This API version is deprecated. Please migrate to /api/v2'
response.headers['X-API-Sunset'] = '2024-12-31'
render json: posts, each_serializer: V1::PostSerializer
end
end
end
end
module Api
module V2
class PostSerializer < ActiveModel::Serializer
attributes :id, :title, :content, :excerpt, :status,
:published_at, :created_at, :updated_at,
:metadata
belongs_to :author, serializer: V2::UserSerializer
has_many :tags
# V2 includes additional computed fields
def metadata
{
word_count: object.body.split.size,
read_time: (object.body.split.size / 200.0).ceil,
featured: object.featured?,
trending: object.trending_score > 10
}
end
# V2 uses different field name
def content
object.body
end
end
end
end
API versioning allows backward-incompatible changes without breaking existing clients. I version via URL path (/api/v1/posts) for simplicity and explicit version selection. Each version namespace has its own controllers and serializers. Routes use namespace blocks to organize versions. When creating v2, I duplicate v1 controllers and modify only what changes, sharing models between versions. For minor changes, I use optional params or conditional logic within a version. Deprecation headers warn clients about sunset timelines. I maintain at least two versions simultaneously during transitions. This strategy balances stability for existing clients with evolution for new features.