supabase
Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
rails-api
REST API specialist for Rails applications. Use when building API endpoints, implementing serialization, API versioning, JWT authentication, or creating API documentation. Focuses on RESTful design, performance, and consistency.
Full skill instructions
Build clean, performant REST APIs with Rails.
RESTful Design:
/api/v1/articles, not /api/v1/get_articles)NEVER create an API without versioning. This is non-negotiable.
Why versioning is required:
Always start with /api/v1/ from day one, even for internal APIs.
# ❌ WRONG - No versioning (will cause pain later)
namespace :api do
resources :articles # Results in: /api/articles
resources :users # Impossible to change structure later
end
# ✅ CORRECT - Versioned from start
namespace :api do
namespace :v1 do
resources :articles # Results in: /api/v1/articles
resources :users # Can add v2 with breaking changes later
end
end
# app/controllers/api/base_controller.rb
class Api::BaseController < ActionController::API
before_action :authenticate
rescue_from ActiveRecord::RecordNotFound, with: :not_found
rescue_from ActiveRecord::RecordInvalid, with: :unprocessable_entity
rescue_from Pundit::NotAuthorizedError, with: :forbidden
private
def not_found(exception)
render json: { error: exception.message }, status: :not_found
end
def unprocessable_entity(exception)
render json: {
error: "Validation failed",
errors: exception.record.errors.full_messages
}, status: :unprocessable_entity
end
def forbidden
render json: { error: "Access denied" }, status: :forbidden
end
end
# app/controllers/api/v1/articles_controller.rb
class Api::V1::ArticlesController < Api::BaseController
before_action :set_article, only: [:show, :update, :destroy]
# GET /api/v1/articles
def index
@articles = Article.published
.page(params[:page])
.per(params[:per_page] || 20)
render json: @articles,
meta: pagination_meta(@articles),
each_serializer: ArticleSerializer
end
# GET /api/v1/articles/:id
def show
render json: @article, serializer: ArticleDetailSerializer
end
# POST /api/v1/articles
def create
@article = current_user.articles.build(article_params)
if @article.save
render json: @article,
status: :created,
location: api_v1_article_url(@article)
else
render json: { errors: @article.errors },
status: :unprocessable_entity
end
end
# PATCH/PUT /api/v1/articles/:id
def update
if @article.update(article_params)
render json: @article
else
render json: { errors: @article.errors },
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
def pagination_meta(collection)
{
current_page: collection.current_page,
total_pages: collection.total_pages,
total_count: collection.total_count,
per_page: collection.limit_value
}
end
end
# app/serializers/article_serializer.rb
class ArticleSerializer < ActiveModel::Serializer
attributes :id, :title, :excerpt, :published_at, :created_at
belongs_to :author, serializer: UserSerializer
has_many :tags
def excerpt
object.body.truncate(200)
end
end
Success:
{
"data": {
"id": 123,
"type": "articles",
"attributes": {
"title": "Article Title",
"excerpt": "Article excerpt..."
},
"relationships": {
"author": { "data": { "id": 1, "type": "users" } }
}
},
"meta": {
"current_page": 1,
"total_pages": 10
}
}
Error:
{
"error": "Validation failed",
"errors": [
"Title can't be blank",
"Body is too short"
]
}
# Gemfile
gem 'jwt'
# app/controllers/api/v1/auth_controller.rb
class Api::V1::AuthController < Api::BaseController
skip_before_action :authenticate, only: [:login]
def login
user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
token = encode_token(user_id: user.id)
render json: {
token: token,
user: UserSerializer.new(user),
expires_at: 24.hours.from_now
}
else
render json: { error: 'Invalid credentials' },
status: :unauthorized
end
end
private
def encode_token(payload)
payload[:exp] = 24.hours.from_now.to_i
JWT.encode(payload, Rails.application.credentials.secret_key_base)
end
end
# Update authenticate method in base controller
def authenticate
token = request.headers['Authorization']&.split(' ')&.last
return render json: { error: 'No token' }, status: :unauthorized unless token
begin
decoded = JWT.decode(token, Rails.application.credentials.secret_key_base)[0]
@current_user = User.find(decoded['user_id'])
rescue JWT::ExpiredSignature
render json: { error: 'Token expired' }, status: :unauthorized
rescue JWT::DecodeError
render json: { error: 'Invalid token' }, status: :unauthorized
end
end
# app/models/user.rb
class User < ApplicationRecord
before_create :generate_api_token
private
def generate_api_token
self.api_token = SecureRandom.hex(32)
end
end
# app/controllers/api/base_controller.rb
def authenticate
authenticate_or_request_with_http_token do |token, options|
@current_user = User.find_by(api_token: token)
end
end
# Gemfile
gem 'rack-attack'
# config/initializers/rack_attack.rb
class Rack::Attack
# Throttle all API requests by IP
throttle('req/ip', limit: 300, period: 5.minutes) do |req|
req.ip if req.path.start_with?('/api/')
end
# Throttle login attempts
throttle('logins/email', limit: 5, period: 20.seconds) do |req|
if req.path == '/api/v1/auth/login' && req.post?
req.params['email'].presence
end
end
end
# config/application.rb
config.middleware.use Rack::Attack
def index
@articles = Article.all
@articles = apply_filters(@articles)
@articles = @articles.page(params[:page]).per(params[:per_page] || 20)
render json: @articles, meta: pagination_meta(@articles)
end
private
def apply_filters(scope)
scope = scope.where(status: params[:status]) if params[:status].present?
scope = scope.where(author_id: params[:author_id]) if params[:author_id].present?
scope = scope.where('created_at >= ?', params[:from_date]) if params[:from_date].present?
scope
end
def index
@articles = Article.includes(:author, :tags)
.page(params[:page])
.per(params[:per_page])
render json: @articles
end
def show
@article = Article.find(params[:id])
if stale?(last_modified: @article.updated_at, etag: @article)
render json: @article
end
end
# spec/requests/api/v1/articles_spec.rb
RSpec.describe 'Articles API', type: :request do
let(:user) { create(:user) }
let(:token) { JWT.encode({ user_id: user.id }, Rails.application.credentials.secret_key_base) }
let(:headers) { { 'Authorization' => "Bearer #{token}" } }
describe 'GET /api/v1/articles' do
it 'returns articles' do
create_list(:article, 3, :published)
get '/api/v1/articles', headers: headers
expect(response).to have_http_status(:ok)
expect(JSON.parse(response.body)['data'].size).to eq(3)
end
end
describe 'POST /api/v1/articles' do
let(:valid_params) do
{ article: { title: 'New Article', body: 'Content' } }
end
it 'creates article' do
expect {
post '/api/v1/articles', params: valid_params, headers: headers
}.to change(Article, :count).by(1)
expect(response).to have_http_status(:created)
end
it 'returns errors for invalid params' do
post '/api/v1/articles', params: { article: { title: '' } }, headers: headers
expect(response).to have_http_status(:unprocessable_entity)
expect(JSON.parse(response.body)['errors']).to be_present
end
end
end
/api/articles instead of /api/v1/articlesFor comprehensive examples and advanced patterns:
api-reference.md (detailed auth, documentation, testing, all patterns)Remember: A good API is versioned from day one, consistent, well-documented, secure, and performant. API versioning is not optional—it's a fundamental requirement.
Handles the full Supabase workflow from schema changes to deployment, with built-in security guardrails that catch common traps like RLS...
Comprehensive guides and best practices for Neon Serverless Postgres, covering setup, connection methods, authentication, and platform APIs.
Guide for setting up and using Firebase Authentication. Use this skill when the user's app requires user sign-in, user management, or secure data access using auth rules.
A skill to evaluate how secure Firestore security rules are. Use this when Firestore security rules are updated to ensure that the generated rules are extremely secure and robust.
Official skill for integrating Firebase AI Logic (Gemini API) into web applications. Covers setup, multimodal inference, structured output, and security.
Complete Better Auth server and client setup with database adapters, session management, plugins, and security configuration.
Deploy and manage projects on Vercel using token-based authentication. Use when working with Vercel CLI using access tokens rather than interactive login — e.g. "deploy to vercel", "set up vercel", "add environment variables to vercel".
Complete Cloudflare platform integration with decision trees for compute, storage, AI, networking, security, and infrastructure-as-code.
Run Azure compliance and security audits with azqr plus Key Vault expiration checks. Covers best-practice assessment, resource review, policy/compliance validation, and security posture checks. WHEN: compliance scan, security audit, BEFORE running azqr (compliance cli tool), Azure best practices, Key Vault expiration check, expired certificates, expiring secrets, orphaned resources, compliance assessment.
Send, read, and manage Gmail messages, drafts, labels, and account settings.
Comprehensive website auditing across 230+ rules in 21 categories including SEO, performance, security, and accessibility.
Shared authentication, CLI syntax, and output formatting patterns for gws Google Workspace commands.
Discover your celebrity doppelgänger with StarByFace!
GeoSpy: Pricing, Features, FAQs, and Alternatives for AI Teams
Detect AI-generated voices to protect against audio fraud.
Ensure Your Content's Originality with AI Plagiarism Checker
Upscale images by 400% without quality loss
Protect your social media from copyright disputes
Accurately Detect AI-Generated Content with TheChecker.AI
Chrome extension that detects and flags AI-generated content