Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :users, only: [:index, :show, :create, :update]
resources :posts do
resources :comments, only: [:index, :create]
end
end
namespace :v2 do
resources :users, only: [:index, :show, :create, :update]
resources :posts do
resources :comments
end
end
end
end
module Api
module V1
class BaseController < ApplicationController
skip_before_action :verify_authenticity_token
before_action :set_default_format
private
def set_default_format
request.format = :json
end
end
end
end
API versioning is critical for maintaining backward compatibility while evolving your endpoints. I use Rails namespace routing to organize versions cleanly within the app/controllers structure. Each version lives in its own module like Api::V1 or Api::V2, which makes it straightforward to override specific endpoints in newer versions while inheriting shared behavior from base controllers. The key advantage is that I can deprecate old versions systematically by removing entire namespaces. I typically include the version in the URL path rather than headers because it's more explicit and easier to test with standard HTTP tools like curl. This pattern also integrates well with API documentation generators that expect conventional RESTful routes.
Related snips
class CommentsController < ApplicationController
before_action :set_post
def create
@comment = @post.comments.build(comment_params)
System test: asserting Turbo Stream responses
class Post < ApplicationRecord
belongs_to :author, class_name: 'User'
has_many :comments, dependent: :destroy
scope :published, -> { where.not(published_at: nil).where('published_at <= ?', Time.current) }
scope :draft, -> { where(published_at: nil) }
ActiveRecord scopes for reusable query logic
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
module Api
module V1
class UsersController < BaseController
def show
user = User.includes(:profile).find(params[:id])
ETags for conditional requests and caching
class PostsController < ApplicationController
def index
@posts = Post.includes(:author)
.order(created_at: :desc)
.page(params[:page])
.per(10)
Turbo Frames: infinite scroll with lazy-loading frame
require "csv"
class PeopleCsvStream
include Enumerable
HEADERS = %w[id full_name email signed_up_at plan].freeze
Resilient CSV Export as a Streamed Response
Share this code
Here's the card — post it anywhere.