Rails API Controllers
Build production-ready RESTful JSON APIs with Rails. This skill covers API controller patterns, versioning, authentication, error handling, and best practices for modern API development.
<when-to-use> - Building JSON APIs for mobile apps, SPAs, or third-party integrations - Creating microservices or API-first applications - Versioning APIs for backward compatibility - Implementing token-based authentication (JWT, API keys) - Adding rate limiting and throttling - Configuring CORS for cross-origin requests - Implementing pagination, filtering, and sorting - Testing API endpoints with RSpec </when-to-use> <benefits> - **RESTful Design** - Follow REST conventions for predictable, maintainable APIs - **Proper Status Codes** - Use correct HTTP status codes for all responses - **Error Handling** - Consistent error responses with meaningful messages - **Versioning** - Support multiple API versions simultaneously - **Authentication** - Token-based auth without sessions or cookies - **Performance** - Efficient JSON rendering and database queries - **Documentation** - Auto-generated API docs with tools like Rswag </benefits> <verification-checklist> Before completing API controller work: - ✅ Proper HTTP status codes used (200, 201, 204, 400, 401, 403, 404, 422, 500) - ✅ Consistent JSON response structure - ✅ Authentication/authorization implemented - ✅ Error handling covers all edge cases - ✅ API tests passing (request specs) - ✅ CORS configured if needed - ✅ Rate limiting configured for production - ✅ API documentation generated/updated </verification-checklist> <standards> - Use `ApplicationController` parent with `ActionController::API` for API-only apps - Return proper HTTP status codes for all responses - Use consistent JSON structure across all endpoints - Implement authentication via tokens (JWT, API keys), NOT sessions - Version APIs via URL path (`/api/v1/`) or Accept header - Handle errors consistently with JSON error responses - Use strong parameters for input validation - Test with request specs, not controller specs - Document APIs with OpenAPI/Swagger - Implement rate limiting to prevent abuse </standards>API-Only Rails Setup
<pattern name="api-only-application"> <description>Create new API-only Rails application</description>Generate API-Only App:
# New API-only Rails app (skips views, helpers, assets)
rails new my_api --api
# Or add to existing app
# config/application.rb
module MyApi
class Application < Rails::Application
config.api_only = true
end
end
Base API Controller:
# app/controllers/application_controller.rb
class ApplicationController < ActionController::API
include ActionController::HttpAuthentication::Token::ControllerMethods
# Global error handling
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity
rescue_from ActionController::ParameterMissing, with: :bad_request
before_action :authenticate
private
def authenticate
authenticate_token || render_unauthorized
end
def authenticate_token
authenticate_with_http_token do |token, options|
@current_user = User.find_by(api_token: token)
end
end
def render_unauthorized
render json: { error: 'Unauthorized' }, status: :unauthorized
end
def not_found(exception)
render json: { error: exception.message }, status: :not_found
end
def unprocessable_entity(exception)
render json: {
error: 'Validation failed',
details: exception.record.errors.full_messages
}, status: :unprocessable_entity
end
def bad_request(exception)
render json: { error: exception.message }, status: :bad_request
end
end
Why: API-only mode removes unnecessary middleware and optimizes for JSON responses. Centralized error handling ensures consistent responses. </pattern>
RESTful API Design
<pattern name="restful-resource-controller"> <description>Standard RESTful API controller with all CRUD actions</description># app/controllers/api/v1/articles_controller.rb
module Api
module V1
class ArticlesController < ApplicationController
before_action :set_article, only: [:show, :update, :destroy]
# GET /api/v1/articles
def index
@articles = Article.published
.includes(:author)
.page(params[:page])
.per(params[:per_page] || 20)
render json: @articles, status: :ok
end
# GET /api/v1/articles/:id
def show
render json: @article, status: :ok
end
# POST /api/v1/articles
def create
@article = Article.new(article_params)
@article.author = current_user
if @article.save
render json: @article, status: :created, location: api_v1_article_url(@article)
else
render json: {
error: 'Failed to create article',
details: @article.errors.full_messages
}, status: :unprocessable_entity
end
end
# PATCH/PUT /api/v1/articles/:id
def update
if @article.update(article_params)
render json: @article, status: :ok
else
render json: {
error: 'Failed to update article',
details: @article.errors.full_messages
}, status: :unprocessable_entity
end
end
# DELETE /api/v1/articles/:id
def destroy
@article.destroy
head :no_content
end
private
def set_article
@article = Article.find(params[:id])
end
def article_params
params.require(:article).permit(:title, :body, :published)
end
end
end
end
Routes:
# config/routes.rb
Rails.application.routes.draw do
namespace :api do
namespace :v1 do
resources :articles
end
end
end
Why: Follows REST conventions with proper status codes (200 OK, 201 Created, 204 No Content, 422 Unprocessable Entity). Namespace by version for future API changes. </pattern>
<pattern name="http-status-codes"> <description>Use correct HTTP status codes for API responses</description>Common Status Codes:
| Code | Symbol | Usage |
|---|---|---|
| 200 | :ok | Successful GET, PATCH, PUT |
| 201 | :created | Successful POST (resource created) |
| 204 | :no_content | Successful DELETE (no response body) |
| 400 | :bad_request | Invalid request syntax, missing parameters |
| 401 | :unauthorized | Missing or invalid authentication |
| 403 | :forbidden | Authenticated but lacks permission |
| 404 | :not_found | Resource doesn't exist |
| 422 | :unprocessable_entity | Validation errors |
| 429 | :too_many_requests | Rate limit exceeded |
| 500 | :internal_server_error | Server error |
Examples:
# Success responses
render json: @article, status: :ok # 200
render json: @article, status: :created # 201
head :no_content # 204
# Error responses
render json: { error: 'Bad request' }, status: :bad_request # 400
render json: { error: 'Unauthorized' }, status: :unauthorized # 401
render json: { error: 'Forbidden' }, status: :forbidden # 403
render json: { error: 'Not found' }, status: :not_found # 404
render json: { error: 'Validation failed' }, status: :unprocessable_entity # 422
Why: Correct status codes help API clients handle responses appropriately and provide clear semantics about what happened. </pattern>
API Versioning
<pattern name="url-versioning"> <description>Version APIs via URL namespace for backward compatibility</description>Directory Structure:
app/controllers/
└── api/
├── v1/
│ ├── articles_controller.rb
│ └── users_controller.rb
└── v2/
├── articles_controller.rb