ruby 115 lines · 3 tabs

Versioned JSON API Namespace in Rails With a Shared Base Controller

Shared by codesnips Jul 2026
3 tabs
Rails.application.routes.draw do
  namespace :api, defaults: { format: :json } do
    namespace :v1 do
      resources :articles, only: [:index, :show, :create, :update, :destroy]
      resources :sessions, only: [:create]
    end
  end
end
3 files · ruby Explain with highlit

A versioned API namespace keeps breaking changes contained: clients pin to /api/v1, and a future /api/v2 can evolve independently without disturbing existing consumers. The pattern shown here centralizes all cross-cutting concerns — authentication, error translation, and response shaping — into a single base controller so that every endpoint in the version inherits consistent behavior.

In routes.rb, the routes are nested under namespace :api with defaults: { format: :json }, then a further namespace :v1. This produces module-scoped controllers like Api::V1::ArticlesController and URL paths like /api/v1/articles. Forcing the JSON format at the namespace level means the API never tries to render HTML or accidentally serves an ERB template.

Api::V1::BaseController is the heart of the design. It inherits from ActionController::API rather than ActionController::Base, dropping view rendering, cookies, and CSRF middleware that a token API does not need. It declares respond_to :json, authenticates every request through a before_action that reads a bearer token, and wires up rescue_from handlers. The rescue_from blocks translate common exceptions — ActiveRecord::RecordNotFound, RecordInvalid, and ActionController::ParameterMissing — into structured JSON with the right HTTP status, so controllers can call find or save! and trust that failures become clean 404 or 422 responses. The render_error and render_success helpers standardize the envelope every endpoint returns.

Api::V1::ArticlesController then reads like plain business logic. Because the base handles auth and errors, show simply calls find, and create uses create! with strong parameters; the bang methods raise, and the base controller catches. index demonstrates lightweight pagination with page and per_page params, capping page size to protect the database.

The main trade-off is inheritance coupling: every controller depends on the base, so shared behavior must stay genuinely universal or be split into concerns. Namespacing by version also duplicates some code across v1 and v2 over time, which teams usually accept as the price of stable, independently deployable API contracts. This structure scales well because new endpoints inherit correctness by default.


Related snips

Share this code

Here's the card — post it anywhere.

Versioned JSON API Namespace in Rails With a Shared Base Controller — share card
Link copied