# Rails API Mode in 2026: RESTful APIs, Serialization and Best Practices > Master Rails API mode with best practices for RESTful design, JSON serialization with Alba and jsonapi-serializer, authentication strategies, and error handling in Rails 8. - Published: 2026-06-12 - Updated: 2026-06-12 - Author: SharpSkill - Tags: ruby-on-rails, api, rest, serialization, best-practices - Reading time: 9 min --- Rails API mode strips away browser-specific middleware to deliver a lean, fast backend purpose-built for JSON APIs. With Rails 8.1 and the growing ecosystem of serialization libraries, building production-grade RESTful APIs has never been more streamlined. > **Quick Takeaway** > > Rails API mode removes sessions, cookies, views, and asset pipeline middleware. The result: a lighter stack optimized for JSON responses, ideal for mobile backends, microservices, and decoupled SPAs. ## Setting Up a Rails 8 API-Only Application Creating an API-only Rails app takes a single flag. The `--api` option configures `ApplicationController` to inherit from `ActionController::API` instead of `ActionController::Base`, removing 15+ middleware layers that serve no purpose in a pure API context. ```ruby # Terminal command rails new order_service --api --database=postgresql ``` This generates a project without view templates, asset compilation, or session cookies. The resulting `application.rb` includes `config.api_only = true`, which keeps the middleware stack minimal. For existing full-stack Rails apps that need an API namespace, the approach differs: create a base API controller that inherits from `ActionController::API` and mount API routes under a versioned namespace. ```ruby # app/controllers/api/v1/base_controller.rb module Api module V1 class BaseController < ActionController::API before_action :authenticate_request private def authenticate_request # Token validation logic end end end end ``` This pattern keeps the full-stack app intact while adding a dedicated API layer. ## RESTful Route Design and Versioning Strategies Clean [RESTful route design](https://guides.rubyonrails.org/routing.html) directly impacts API usability and maintainability. Rails routing DSL makes it straightforward to express resource hierarchies, but a few conventions separate solid APIs from fragile ones. ```ruby # config/routes.rb Rails.application.routes.draw do namespace :api do namespace :v1 do resources :users, only: [:index, :show, :create, :update] do resources :orders, only: [:index, :show, :create] end resources :products, only: [:index, :show] do collection do get :search end end resource :session, only: [:create, :destroy] end end end ``` Key conventions: - **Namespace versioning** (`/api/v1/`) over header-based versioning for simplicity and cacheability - **Shallow nesting** limited to one level: `/users/:user_id/orders` works, but `/users/:user_id/orders/:order_id/items` should become `/orders/:order_id/items` - **Singular resources** for endpoints representing the current user's session or profile > **Version Deprecation** > > When retiring an API version, return HTTP 410 Gone with a JSON body pointing to the new version rather than silently breaking clients. ## JSON Serialization: Alba vs. jsonapi-serializer Serialization determines how ActiveRecord objects become JSON. The choice of serializer affects response times, payload structure, and API contract flexibility. Two libraries dominate the Rails ecosystem in 2026: [Alba](https://okuramasafumi.github.io/alba/) for speed and simplicity, and [jsonapi-serializer](https://github.com/jsonapi-serializer/jsonapi-serializer) for JSON:API spec compliance. ### Alba: Zero-Dependency Performance Alba serializes Ruby objects up to 10x faster than legacy alternatives like ActiveModel::Serializer. It has zero dependencies, making it ideal for lightweight API services. ```ruby # app/resources/user_resource.rb class UserResource include Alba::Resource root_key :user, :users attributes :id, :email, :name, :created_at attribute :full_name do |user| "#{user.first_name} #{user.last_name}" end many :orders, resource: OrderResource # Conditional attributes based on context attribute :admin_notes, if: proc { |user, params| params[:current_user]&.admin? } end ``` ```ruby # app/controllers/api/v1/users_controller.rb class Api::V1::UsersController < Api::V1::BaseController def show user = User.includes(:orders).find(params[:id]) render json: UserResource.new(user, params: { current_user: current_user }) end def index users = User.where(active: true).page(params[:page]) render json: UserResource.new(users) end end ``` ### jsonapi-serializer: Strict Spec Compliance When API consumers expect [JSON:API](https://jsonapi.org/) formatted responses with `data`, `type`, `attributes`, and `relationships` keys, `jsonapi-serializer` (the maintained fork of Netflix's fast_jsonapi) handles the formatting automatically. ```ruby # app/serializers/user_serializer.rb class UserSerializer include JSONAPI::Serializer set_type :user set_id :id attributes :email, :name, :created_at has_many :orders, serializer: OrderSerializer # Cache at the serializer level for high-traffic endpoints cache_options store: Rails.cache, namespace: "jsonapi", expires_in: 1.hour end ``` The choice depends on the project: Alba for internal APIs, microservices, and mobile backends where payload flexibility matters. jsonapi-serializer for public APIs where standardized contracts reduce integration friction. ## Authentication Patterns for Rails APIs Rails 8 introduced a built-in [authentication generator](https://guides.rubyonrails.org/security.html) that scaffolds session-based auth. Adapting this for API-only mode requires swapping cookie sessions for token-based authentication. Two patterns dominate: JWT for stateless architectures and opaque bearer tokens for simpler revocation. ### JWT with Short-Lived Tokens ```ruby # app/services/jwt_service.rb class JwtService SECRET = Rails.application.credentials.jwt_secret_key ALGORITHM = "HS256" def self.encode(payload, exp: 15.minutes.from_now) payload[:exp] = exp.to_i JWT.encode(payload, SECRET, ALGORITHM) end def self.decode(token) body = JWT.decode(token, SECRET, true, algorithm: ALGORITHM).first HashWithIndifferentAccess.new(body) rescue JWT::ExpiredSignature, JWT::DecodeError => e nil end end ``` ```ruby # app/controllers/concerns/jwt_authenticatable.rb module JwtAuthenticatable extend ActiveSupport::Concern included do before_action :authenticate_request end private def authenticate_request token = request.headers["Authorization"]&.split(" ")&.last decoded = JwtService.decode(token) if decoded @current_user = User.find_by(id: decoded[:user_id]) end render json: { error: "Unauthorized" }, status: :unauthorized unless @current_user end def current_user @current_user end end ``` Short-lived access tokens (15 minutes) paired with refresh token rotation provide a secure balance. The access token stays stateless, while refresh tokens stored in the database allow revocation on password change or logout. ### Opaque Bearer Tokens For simpler APIs where database lookups per request are acceptable, `has_secure_token` offers a straightforward approach: ```ruby # app/models/user.rb class User < ApplicationRecord has_secure_password has_secure_token :api_token def regenerate_api_token! regenerate_api_token end end ``` Opaque tokens simplify revocation (delete or regenerate the token) but require a database query on every authenticated request. ## Structured Error Handling Across the API Consistent error responses separate professional APIs from prototypes. A centralized error handler prevents Rails from returning HTML error pages and ensures every failure returns structured JSON. ```ruby # app/controllers/concerns/error_handler.rb module ErrorHandler extend ActiveSupport::Concern included do rescue_from ActiveRecord::RecordNotFound, with: :not_found rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity rescue_from ActionController::ParameterMissing, with: :bad_request end private def not_found(exception) render json: { error: "not_found", message: "Resource not found", details: exception.message }, status: :not_found end def unprocessable_entity(exception) render json: { error: "validation_failed", message: "Validation failed", details: exception.record.errors.full_messages }, status: :unprocessable_entity end def bad_request(exception) render json: { error: "bad_request", message: "Missing required parameter", details: exception.message }, status: :bad_request end end ``` Include this concern in the base API controller. Every endpoint then returns predictable JSON errors with appropriate HTTP status codes, machine-readable error types, and human-readable messages. > **Skip CSRF for Token Auth** > > API controllers using token-based authentication must skip CSRF verification. Add `skip_before_action :verify_authenticity_token` or inherit from `ActionController::API`, which does not include CSRF middleware by default. ## Pagination and Response Optimization Unbounded queries are the fastest path to performance degradation. Every list endpoint should paginate results and communicate pagination metadata clearly. ```ruby # app/controllers/api/v1/products_controller.rb class Api::V1::ProductsController < Api::V1::BaseController def index products = Product .where(active: true) .order(created_at: :desc) .page(params[:page]) .per(params[:per_page] || 25) render json: { data: ProductResource.new(products).serializable_hash, meta: { current_page: products.current_page, total_pages: products.total_pages, total_count: products.total_count } } end end ``` Beyond pagination, three optimizations make a measurable difference in API response times: - **Eager loading** with `includes` or `preload` eliminates [N+1 queries](/blog/ruby-on-rails/activerecord-fixing-n-plus-1-query-problems) that multiply database round-trips - **Select only needed columns** with `.select(:id, :name, :price)` when serializers use a subset of model attributes - **HTTP caching headers** via `stale?` and `fresh_when` allow clients and CDNs to cache responses without custom logic ## Testing Rails API Endpoints with RSpec API tests should verify status codes, response structure, and authentication gates. [Request specs](https://guides.rubyonrails.org/testing.html) in RSpec hit the full middleware stack, making them the closest representation of real API behavior. ```ruby # spec/requests/api/v1/users_spec.rb RSpec.describe "Api::V1::Users", type: :request do let(:user) { create(:user) } let(:token) { JwtService.encode(user_id: user.id) } let(:headers) { { "Authorization" => "Bearer #{token}" } } describe "GET /api/v1/users/:id" do it "returns the user with correct structure" do get "/api/v1/users/#{user.id}", headers: headers expect(response).to have_http_status(:ok) json = JSON.parse(response.body) expect(json["user"]).to include( "id" => user.id, "email" => user.email, "name" => user.name ) end it "returns 401 without authentication" do get "/api/v1/users/#{user.id}" expect(response).to have_http_status(:unauthorized) end it "returns 404 for non-existent user" do get "/api/v1/users/0", headers: headers expect(response).to have_http_status(:not_found) json = JSON.parse(response.body) expect(json["error"]).to eq("not_found") end end end ``` These tests cover the three scenarios every API endpoint should handle: successful response structure, authentication enforcement, and error response format. ## Rails 8.1 API Features Worth Adopting Rails 8.1 (released October 2025) adds capabilities directly relevant to API development: - **Continuable Jobs** allow long-running background tasks (data imports, batch processing) to resume from the last checkpoint after deploys or restarts, eliminating wasted work in [background job pipelines](/technologies/ruby-on-rails/interview-questions/active-job-background-jobs) - **Structured Event Logging** via `Rails.event.notify(...)` emits events consumable by APM platforms (Datadog, New Relic) without custom instrumentation code - **Deprecated Associations** can be marked with `:warn`, `:raise`, or `:notify` modes, helping teams phase out legacy relationships in large API codebases These features reduce boilerplate in API projects and improve observability out of the box. The [Rails 8 migration guide](/blog/ruby-on-rails/ruby-on-rails-8-new-features-migration-guide) covers the full upgrade path. ## Conclusion - **Use `--api` mode** for dedicated API services to eliminate unnecessary middleware and reduce response latency - **Pick a serializer that fits the contract**: Alba for speed and flexibility, jsonapi-serializer for strict JSON:API compliance - **Implement token authentication** with short-lived JWTs and refresh rotation, or opaque bearer tokens for simpler revocation needs - **Centralize error handling** in a shared concern so every endpoint returns structured JSON with consistent error types - **Paginate every list endpoint** and apply eager loading to eliminate N+1 queries before they reach production - **Test request specs** against the three core scenarios: success structure, auth enforcement, and error format - **Adopt Rails 8.1 features** like continuable jobs and structured events to improve reliability and observability in API services --- Source: SharpSkill (https://sharpskill.dev), tech interview preparation for your real stack. HTML version of this page: https://sharpskill.dev/en/blog/ruby-on-rails/rails-api-mode-restful-serialization-best-practices