sinatra
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