Agent skill
sinatra
Sinatra framework guardrails, patterns, and best practices for AI-assisted development. Use when working with Sinatra projects, or when the user mentions Sinatra. Provides lightweight Ruby web patterns, routing DSL, middleware, and REST API guidelines.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/sinatra-ar4mirez-samuel
Metadata
Additional technical details for this skill
- author
- samuel
- version
- 1.0
- category
- framework
- language
- ruby
- extensions
- .rb
SKILL.md
Sinatra Framework Guide
Applies to: Sinatra 3.x, Ruby 3.0+, REST APIs, Microservices, Prototypes Language Guide: @.claude/skills/ruby-guide/SKILL.md
Overview
Sinatra is a lightweight DSL for building web applications and APIs in Ruby. It maps HTTP verbs directly to Ruby blocks, providing minimal ceremony and maximum flexibility.
Use Sinatra when:
- Building microservices or small-to-medium APIs
- Rapid prototyping or proof-of-concept work
- Lightweight webhooks, proxies, or internal tools
- You want minimal framework overhead
Consider alternatives when:
- You need a full MVC framework (use Rails)
- You need built-in admin, ORM, and auth out of the box (use Rails)
- Application complexity exceeds 10+ route modules (use Rails or Hanami)
Guardrails
Sinatra-Specific Guidelines
- Use modular style (
Sinatra::Base) for production applications - Classic style is acceptable for scripts and prototypes only
- Use
sinatra-contribfor JSON, namespaces, and reloader support - Use Puma as the production application server
- Use Bundler for dependency management with a Gemfile
- Separate routes into modules via
registerandSinatra::Namespace - Extract helpers into dedicated modules under
app/helpers/ - Use service objects for business logic (keep routes thin)
Security Guidelines
- Never hardcode secrets; use environment variables via
dotenv - Set
session_secretfromENV(not random fallback in production) - Validate all user input before processing
- Use parameterized queries via ActiveRecord or Sequel ORM
- Implement authentication checks with
beforefilters - Use HTTPS in production with proper HSTS headers
- Implement rate limiting via Rack middleware
- Configure CORS restrictively (never
*in production)
Testing Guidelines
- Use RSpec with
rack-testfor HTTP integration tests - Use
database_cleanerto reset state between tests - Test both success and error paths for every endpoint
- Mock external services (never call real APIs in tests)
- Use factory_bot for test data setup
- Coverage target: >80% for business logic
Project Structure
Simple Application (Prototypes Only)
myapp/
├── app.rb # All routes and config
├── config.ru # Rack configuration
├── Gemfile
├── public/ # Static assets
├── views/ # ERB/Haml templates
│ ├── layout.erb
│ └── index.erb
└── spec/
└── app_spec.rb
Modular Application (Production)
myapp/
├── config.ru # Rack entry point
├── Gemfile
├── Rakefile # Database tasks
├── app/
│ ├── main.rb # Sinatra::Base application class
│ ├── routes/ # Route modules (one per resource)
│ │ ├── auth.rb
│ │ ├── users.rb
│ │ └── posts.rb
│ ├── models/ # ActiveRecord/Sequel models
│ │ ├── user.rb
│ │ └── post.rb
│ ├── services/ # Business logic (no Sinatra imports)
│ │ └── user_service.rb
│ └── helpers/ # Reusable helper modules
│ ├── auth_helper.rb
│ └── response_helper.rb
├── config/
│ ├── database.yml
│ └── environment.rb # Boot: dotenv, bundler, DB setup
├── db/
│ └── migrations/
├── public/
├── views/
└── spec/
├── spec_helper.rb
├── routes/
└── models/
Key conventions:
app/main.rbcontains theSinatra::Basesubclass and wires everything togetherapp/routes/holds one module per resource, registered viaregisterapp/services/holds business logic with no framework couplingapp/helpers/holds modules included viahelpersDSLconfig/environment.rbboots dotenv, bundler, and database connection
Routing DSL
Sinatra maps HTTP verbs directly to Ruby blocks. Parameters are captured via :name symbols.
# Basic CRUD routes
get '/users' { json users: User.all.map(&:to_h) }
get '/users/:id' { json user: find_user!.to_h }
post '/users' { create_user }
put '/users/:id' { update_user }
delete '/users/:id' { delete_user }
Route Parameters
# Named parameters (available in params hash)
get '/users/:id' do
user = User.find(params[:id])
json user: user.to_h
end
# Splat (wildcard) parameters
get '/files/*.*' do
# params['splat'] => ['path/to/file', 'ext']
end
# Query parameters
get '/search' do
# params[:q], params[:page]
end
# Regular expression routes
get %r{/posts/(\d+)} do |id|
Post.find(id)
end
Namespaced Routes
Use sinatra/namespace to group routes with shared prefixes.
require 'sinatra/namespace'
class App < Sinatra::Base
register Sinatra::Namespace
namespace '/api/v1' do
namespace '/users' do
get { json users: User.all.map(&:to_h) }
post { create_user }
get '/:id' do
json user: User.find(params[:id]).to_h
end
end
end
end
Request & Response
Reading Request Data
# JSON body (use a helper for safety)
def json_params
@json_params ||= begin
body = request.body.read
body.empty? ? {} : JSON.parse(body, symbolize_names: true)
end
rescue JSON::ParserError
halt 400, json(error: 'Invalid JSON')
end
# Form data
params[:field_name]
# Headers
request.env['HTTP_AUTHORIZATION']
request.content_type
request.accept?('application/json')
Response Patterns
# JSON response (via sinatra/json)
get '/health' do
json status: 'ok', timestamp: Time.now.iso8601
end
# Status codes
post '/users' do
user = User.create!(json_params)
status 201
json user: user.to_h
end
# Halt with error (stops execution immediately)
halt 404, json(error: 'Not found')
halt 401, json(error: 'Unauthorized')
halt 422, json(errors: user.errors.full_messages)
# Redirect
redirect '/login'
redirect '/users', 303 # See Other
# Headers
headers 'X-Custom-Header' => 'value'
content_type :json
Helpers
Define reusable methods available in routes and views.
# In modular style, define helper modules
module AuthHelper
def current_user
@current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
end
def require_login!
halt 401, json(error: 'Unauthorized') unless current_user
end
def require_admin!
require_login!
halt 403, json(error: 'Forbidden') unless current_user.admin?
end
end
module ResponseHelper
def paginate(collection, per_page: 20)
page = (params[:page] || 1).to_i
per = (params[:per_page] || per_page).to_i
total = collection.count
items = collection.offset((page - 1) * per).limit(per)
{ items: items, meta: { page: page, per_page: per, total: total } }
end
end
# Register in the app
class App < Sinatra::Base
helpers AuthHelper
helpers ResponseHelper
end
Views & Templates
Sinatra supports ERB, Haml, Slim, and other Tilt-compatible template engines. For JSON APIs, skip views entirely and use the json helper.
# Render a template (looks in views/ directory)
get '/' do
@title = 'Home'
erb :index # renders views/index.erb
end
# Custom layout
get '/admin' do
erb :dashboard, layout: :admin_layout
end
Use template inheritance via <%= yield %> in views/layout.erb. Keep logic out of templates; use helpers and instance variables set in routes.
Error Handling
Error Handlers
class App < Sinatra::Base
# Named error handlers
not_found do
json error: 'Not found'
end
error do
json error: 'Internal server error'
end
# Catch specific exceptions
error ActiveRecord::RecordNotFound do
status 404
json error: 'Resource not found'
end
error ActiveRecord::RecordInvalid do |e|
status 422
json errors: e.record.errors.full_messages
end
error JSON::ParserError do
status 400
json error: 'Invalid JSON'
end
end
Environment-Specific Error Display
configure :development do
enable :show_exceptions
enable :logging
end
configure :production do
disable :show_exceptions
enable :raise_errors
end
Filters
Use before and after filters for cross-cutting concerns.
class App < Sinatra::Base
# Run before every request
before do
content_type :json if request.accept?('application/json')
end
# Scoped to path prefix
before '/api/*' do
content_type :json
authenticate_token!
end
# Run after every request
after do
response.headers['X-Request-Id'] = SecureRandom.uuid
end
end
Modular Application Pattern
Application Class (app/main.rb)
require 'sinatra/base'
require 'sinatra/json'
require 'sinatra/namespace'
class App < Sinatra::Base
register Sinatra::Namespace
configure do
set :server, :puma
set :root, File.dirname(__FILE__)
set :views, proc { File.join(root, '..', 'views') }
enable :sessions
set :session_secret, ENV.fetch('SESSION_SECRET')
end
configure :development do
require 'sinatra/reloader'
register Sinatra::Reloader
enable :logging
end
Dir[File.join(__dir__, 'helpers', '*.rb')].each { |f| require f }
helpers AuthHelper, ResponseHelper
Dir[File.join(__dir__, 'routes', '*.rb')].each { |f| require f }
register Routes::Users
register Routes::Auth
get('/health') { json status: 'ok' }
not_found { json error: 'Not found' }
error { json error: 'Internal server error' }
end
Route Module (app/routes/users.rb)
module Routes
module Users
def self.registered(app)
app.namespace '/api/v1/users' do
get { json users: User.all.map(&:to_h) }
get('/:id') { json user: User.find(params[:id]).to_h }
post do
user = User.create!(json_params)
status 201
json user: user.to_h
rescue ActiveRecord::RecordInvalid => e
status 422
json errors: e.record.errors.full_messages
end
end
end
end
end
Rack Configuration (config.ru)
require 'bundler/setup'
Bundler.require(:default, ENV.fetch('RACK_ENV', 'development'))
require_relative 'config/environment'
require_relative 'app/main'
run App
Commands Reference
# Install dependencies
bundle install
# Run development server (with auto-reload)
bundle exec ruby app.rb
# Or with rackup
bundle exec rackup -p 4567
# Run tests
bundle exec rspec
bundle exec rspec --format documentation
# Run with coverage
bundle exec rspec --format progress --require simplecov
# Database tasks
bundle exec rake db:create
bundle exec rake db:migrate
bundle exec rake db:rollback
# Production server
bundle exec puma -C config/puma.rb
# Console (interactive)
bundle exec pry -r ./config/environment
# Docker
docker build -t myapp .
docker run -p 4567:4567 myapp
Dependencies
| Gem | Purpose |
|---|---|
sinatra |
Web framework DSL |
sinatra-contrib |
Extensions: JSON, namespace, reloader |
puma |
Production application server |
rake |
Task runner for migrations |
dotenv |
Environment variable loading |
activerecord / sequel |
ORM for database access |
pg |
PostgreSQL adapter |
bcrypt |
Password hashing (has_secure_password) |
jwt |
JSON Web Token authentication |
rspec |
Test framework |
rack-test |
HTTP request helpers for Rack apps |
database_cleaner-active_record |
DB cleanup between tests |
factory_bot |
Test data factories |
Advanced Topics
For detailed code examples and advanced patterns, see:
- references/patterns.md -- Database integration, authentication (sessions and JWT), Rack middleware, testing patterns, deployment, and Sequel ORM alternative
External References
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?