AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified MIT Self-run

Rails Api Controllers

skill-shoebtamboli-rails-claude-skills-rails-api-controllers · by Shoebtamboli

RESTful API controller patterns for Ruby on Rails. Use when: (1) Building JSON APIs, (2) API versioning, (3) Error handling and status codes, (4) Authentication with tokens/JWT, (5) Rate limiting, (6) CORS configuration, (7) Pagination and filtering, (8) API documentation, (9) Testing API endpoints

No reviews yet
0 installs
39 views
0.0% view→install

Install

$ agentstack add skill-shoebtamboli-rails-claude-skills-rails-api-controllers

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access Used
  • Filesystem access No
  • Shell / process execution No
  • Environment & secrets Used
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-shoebtamboli-rails-claude-skills-rails-api-controllers)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
2mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Rails Api Controllers? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

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.

  • 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
  • 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

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
  • 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

API-Only Rails Setup

Create new API-only Rails application

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 

---

## RESTful API Design

Standard RESTful API controller with all CRUD actions

```ruby
# app/controllers/api/v1/articles_controller.rb
module Api
  module V1
    class ArticlesController 

Use correct HTTP status codes for API responses

**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:**

```ruby
# 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.


API Versioning

Version APIs via URL namespace for backward compatibility

Directory Structure:

app/controllers/
└── api/
    ├── v1/
    │   ├── articles_controller.rb
    │   └── users_controller.rb
    └── v2/
        ├── articles_controller.rb
        └── users_controller.rb

V1 Controller:

# app/controllers/api/v1/articles_controller.rb
module Api
  module V1
    class ArticlesController 

Breaking API changes without versioning

```ruby
# ❌ WRONG - Breaking existing clients
class Api::ArticlesController 

```ruby
# ✅ CORRECT - New version for breaking changes
module Api
  module V1
    class ArticlesController 

**Why bad:** Breaking changes without versioning break existing API clients. Always version when changing response structure or behavior.

---

## Authentication & Authorization

Token-based authentication for stateless APIs

**User Model:**

```ruby
# app/models/user.rb
class User 

JWT (JSON Web Token) authentication for APIs

**Setup:**

```ruby
# Gemfile
gem 'jwt'

# lib/json_web_token.rb
class JsonWebToken
  SECRET_KEY = Rails.application.credentials.secret_key_base

  def self.encode(payload, exp = 24.hours.from_now)
    payload[:exp] = exp.to_i
    JWT.encode(payload, SECRET_KEY)
  end

  def self.decode(token)
    body = JWT.decode(token, SECRET_KEY)[0]
    HashWithIndifferentAccess.new(body)
  rescue JWT::DecodeError, JWT::ExpiredSignature
    nil
  end
end

Application Controller:

# app/controllers/application_controller.rb
class ApplicationController 

---

## Pagination, Filtering & Sorting

Paginate API responses with Kaminari or Pagy

**With Kaminari:**

```ruby
# Gemfile
gem 'kaminari'

# app/controllers/api/v1/articles_controller.rb
def index
  page = params[:page] || 1
  per_page = params[:per_page] || 20

  @articles = Article.page(page).per(per_page)

  render json: {
    data: @articles,
    meta: {
      current_page: @articles.current_page,
      next_page: @articles.next_page,
      prev_page: @articles.prev_page,
      total_pages: @articles.total_pages,
      total_count: @articles.total_count
    }
  }
end

With Pagy (Faster):

# Gemfile
gem 'pagy'

# app/controllers/application_controller.rb
include Pagy::Backend

# app/controllers/api/v1/articles_controller.rb
def index
  pagy, articles = pagy(Article.all, items: params[:per_page] || 20)

  render json: {
    data: articles,
    meta: {
      current_page: pagy.page,
      total_pages: pagy.pages,
      total_count: pagy.count,
      per_page: pagy.items
    }
  }
end

Why: Pagination prevents loading large datasets into memory. Include metadata so clients know how to fetch more pages.

Allow clients to filter and sort resources

# app/controllers/api/v1/articles_controller.rb
def index
  @articles = Article.all

  # Filtering
  @articles = @articles.where(status: params[:status]) if params[:status].present?
  @articles = @articles.where(category: params[:category]) if params[:category].present?
  @articles = @articles.where('created_at >= ?', params[:from_date]) if params[:from_date].present?

  # Searching
  @articles = @articles.where('title ILIKE ?', "%#{params[:q]}%") if params[:q].present?

  # Sorting
  sort_column = params[:sort_by] || 'created_at'
  sort_direction = params[:order] || 'desc'
  @articles = @articles.order("#{sort_column} #{sort_direction}")

  # Pagination
  @articles = @articles.page(params[:page]).per(params[:per_page] || 20)

  render json: {
    data: @articles,
    meta: pagination_meta(@articles)
  }
end

private

def pagination_meta(collection)
  {
    current_page: collection.current_page,
    total_pages: collection.total_pages,
    total_count: collection.total_count
  }
end

Example Requests:

# Filter by status
GET /api/v1/articles?status=published

# Search by title
GET /api/v1/articles?q=rails

# Sort by created_at descending
GET /api/v1/articles?sort_by=created_at&order=desc

# Combine filters, search, sort, and pagination
GET /api/v1/articles?status=published&q=rails&sort_by=title&order=asc&page=2&per_page=50

Why: Flexible filtering and sorting let clients fetch exactly what they need without loading unnecessary data.


CORS Configuration

Configure CORS to allow cross-origin API requests

Setup:

# Gemfile
gem 'rack-cors'

# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    origins 'example.com', 'localhost:3000'  # Whitelist specific origins

    resource '/api/*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head],
      credentials: true,
      max_age: 86400  # Cache preflight for 24 hours
  end
end

Development (Allow All Origins):

# config/initializers/cors.rb
Rails.application.config.middleware.insert_before 0, Rack::Cors do
  allow do
    if Rails.env.development?
      origins '*'  # Allow all in development
    else
      origins ENV['ALLOWED_ORIGINS']&.split(',') || 'example.com'
    end

    resource '/api/*',
      headers: :any,
      methods: [:get, :post, :put, :patch, :delete, :options, :head]
  end
end

Why: CORS is required when frontend (SPA, mobile app) and API are on different domains. Whitelist specific origins in production for security.


Rate Limiting

Implement rate limiting to prevent API abuse

With Rack::Attack:

# Gemfile
gem 'rack-attack'

# config/initializers/rack_attack.rb
class Rack::Attack
  # Throttle all requests by IP (60 requests per minute)
  throttle('req/ip', limit: 60, period: 1.minute) do |req|
    req.ip if req.path.start_with?('/api/')
  end

  # Throttle POST requests by IP (10 per minute)
  throttle('req/ip/post', limit: 10, period: 1.minute) do |req|
    req.ip if req.path.start_with?('/api/') && req.post?
  end

  # Throttle authenticated requests by user token
  throttle('req/token', limit: 100, period: 1.minute) do |req|
    if req.path.start_with?('/api/')
      token = req.env['HTTP_AUTHORIZATION']&.split(' ')&.last
      User.find_by(api_token: token)&.id if token
    end
  end

  # Custom response for throttled requests
  self.throttled_responder = lambda do |env|
    [
      429,
      { 'Content-Type' => 'application/json' },
      [{ error: 'Rate limit exceeded. Try again later.' }.to_json]
    ]
  end
end

# config/application.rb
config.middleware.use Rack::Attack

Why: Rate limiting prevents abuse, protects server resources, and ensures fair usage across all API clients.


Error Handling

Standardized error response format

# app/controllers/application_controller.rb
class ApplicationController 

---

## Testing API Endpoints

Test API endpoints with RSpec request specs

```ruby
# spec/requests/api/v1/articles_spec.rb
require 'rails_helper'

RSpec.describe 'Api::V1::Articles', type: :request do
  let(:user) { create(:user) }
  let(:headers) { { 'Authorization' => "Token #{user.api_token}" } }

  describe 'GET /api/v1/articles' do
    let!(:articles) { create_list(:article, 3, :published) }

    it 'returns all published articles' do
      get '/api/v1/articles', headers: headers

      expect(response).to have_http_status(:ok)
      expect(json_response['data'].size).to eq(3)
    end

    it 'filters by status' do
      draft = create(:article, status: :draft)

      get '/api/v1/articles', params: { status: 'draft' }, headers: headers

      expect(response).to have_http_status(:ok)
      expect(json_response['data'].size).to eq(1)
      expect(json_response['data'].first['id']).to eq(draft.id)
    end

    it 'paginates results' do
      create_list(:article, 25)

      get '/api/v1/articles', params: { page: 2, per_page: 10 }, headers: headers

      expect(response).to have_http_status(:ok)
      expect(json_response['data'].size).to eq(10)
      expect(json_response['meta']['current_page']).to eq(2)
    end
  end

  describe 'POST /api/v1/articles' do
    let(:valid_attributes) { { article: { title: 'Test', body: 'Content' } } }

    it 'creates a new article' do
      expect {
        post '/api/v1/articles', params: valid_attributes, headers: headers
      }.to change(Article, :count).by(1)

      expect(response).to have_http_status(:created)
      expect(json_response['title']).to eq('Test')
      expect(response.location).to be_present
    end

    it 'returns errors for invalid data' do
      post '/api/v1/articles', params: { article: { title: '' } }, headers: headers

      expect(response).to have_http_status(:unprocessable_entity)
      expect(json_response['error']).to eq('Failed to create article')
      expect(json_response['details']).to include("Title can't be blank")
    end
  end

  describe 'DELETE /api/v1/articles/:id' do
    let!(:article) { create(:article) }

    it 'deletes the article' do
      expect {
        delete "/api/v1/articles/#{article.id}", headers: headers
      }.to change(Article, :count).by(-1)

      expect(response).to have_http_status(:no_content)
      expect(response.body).to be_empty
    end
  end

  describe 'authentication' do
    it 'returns 401 without token' do
      get '/api/v1/articles'

      expect(response).to have_http_status(:unauthorized)
      expect(json_response['error']).to eq('Unauthorized')
    end

    it 'returns 401 with invalid token' do
      get '/api/v1/articles', headers: { 'Authorization' => 'Token invalid' }

      expect(response).to have_http_status(:unauthorized)
    end
  end

  private

  def json_response
    JSON.parse(response.body)
  end
end

Why: Request specs test the full HTTP request/response cycle including routing, authentication, and JSON parsing. More realistic than controller specs.


# spec/support/request_helpers.rb
module RequestHelpers
  def json_response
    JSON.parse(response.body)
  end

  def auth_headers(user)
    { 'Authorization' => "Token #{user.api_token}" }
  end
end

RSpec.configure do |config|
  config.include RequestHelpers, type: :request
end

# spec/requests/api/v1/authentication_spec.rb
RSpec.describe 'Api::V1::Authentication', type: :request do
  describe 'POST /api/v1/auth' do
    let(:user) { create(:user, email: 'test@example.com', password: 'password') }

    it 'returns token with valid credentials' do
      post '/api/v1/auth', params: { email: 'test@example.com', password: 'password' }

      expect(response).to have_http_status(:ok)
      expect(json_response['token']).to be_present
      expect(json_response['user']['email']).to eq('test@example.com')
    end

    it 'returns error with invalid credentials' do
      post '/api/v1/auth', params: { email: 'test@example.com', password: 'wrong' }

      expect(response).to have_http_status(:unauthorized)
      expect(json_response['error']).to eq('Invalid email or password')
    end
  end
end

  • rails-ai:models - Model patterns for API resources
  • rails-ai:serializers - JSON serialization (ActiveModelSerializers, Blueprinter)
  • rails-ai:testing - Testing patterns for API endpoints
  • rails-ai:auth-with-devise - Token-based authentication with Devise
  • rails-ai:jobs - Background processing for async API operations

Official Documentation:

Gems & Libraries:

API Documentation:

Best Practices:

  • [R

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.