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
ApplicationControllerparent withActionController::APIfor 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