smithery/geoffjay

sinatra-patterns

Common Sinatra patterns, routing strategies, error handling, and application organization. Use when building Sinatra applications or designing routes.

Installation

$ npx skills add smithery/geoffjay --skill sinatra-patterns

Similar popular skills

Related neighbors and high-traction skills in the same topics — useful to compare before installing.

Also in this package

Other skills from smithery/geoffjay · top by installs.

npx skills add smithery/geoffjay

Browse all from smithery/geoffjay

More details

Agent compatibility

Declared targets from SKILL.md / docs. Unmarked agents are not listed — the skill may still install via the CLI.

Claude Code Not declared
Cursor Not declared
Codex Not declared
GitHub Copilot Not declared
Windsurf Not declared
Gemini CLI Not declared
Cline Not declared
OpenCode Not declared

Package contents

Files included with this skill beyond the listing page.

  • skill md SKILL.md 11,074 B
  • docs SUMMARY.md 174 B

History

  1. First recorded snapshot · 0 installs

SKILL.md

Sinatra Patterns Skill

Tier 1: Quick Reference

Common Routing Patterns

Basic Routes:

get '/' do
  'Hello World'
end

post '/users' do
  # Create user
end

put '/users/:id' do
  # Update user
end

delete '/users/:id' do
  # Delete user
end

Route Parameters:

# Named parameters
get '/users/:id' do
  User.find(params[:id])
end

# Parameter constraints
get '/users/:id', :id => /\d+/ do
  # Only matches numeric IDs
end

# Wildcard
get '/files/*.*' do
  # params['splat'] contains matched segments
end

Query Parameters:

get '/search' do
  query = params[:q]
  page = params[:page] || 1
  results = search(query, page: page)
end

Basic Middleware

# Session middleware
use Rack::Session::Cookie, secret: ENV['SESSION_SECRET']

# Security middleware
use Rack::Protection

# Logging
use Rack::CommonLogger

# Compression
use Rack::Deflater

Simple Error Handling

not_found do
  'Page not found'
end

error do
  'Internal server error'
end

error 401 do
  'Unauthorized'
end

Helpers

helpers do
  def logged_in?
    !session[:user_id].nil?
  end

  def current_user
    @current_user ||= User.find_by(id: session[:user_id])
  end
end

Tier 2: Detailed Instructions

Advanced Routing

Modular Applications:

# app/controllers/base_controller.rb
class BaseController < Sinatra::Base
  configure do
    set :views, Proc.new { File.join(root, '../views') }
    set :public_folder, Proc.new { File.join(root, '../public') }
  end

  helpers do
    def json_response(data, status = 200)
      content_type :json
      halt status, data.to_json
    end
  end
end

# app/controllers/users_controller.rb
class UsersController < BaseController
  get '/' do
    users = User.all
    json_response(users.map(&:to_hash))
  end

  get '/:id' do
    user = User.find(params[:id]) || halt(404)
    json_response(user.to_hash)
  end

  post '/' do
    user = User.create(params[:user])
    if user.persisted?
      json_response(user.to_hash, 201)
    else
      json_response({ errors: user.errors }, 422)
    end
  end
end

# config.ru
map '/users' do
  run UsersController
end

Namespaces:

require 'sinatra/namespace'

class App < Sinatra::Base
  register Sinatra::Namespace

  namespace '/api' do
    namespace '/v1' do
      get '/users' do
        # GET /api/v1/users
      end

      namespace '/admin' do
        before do
          authenticate_admin!
        end

        get '/stats' do
          # GET /api/v1/admin/stats
        end
      end
    end
  end
end

Route Conditions:

# User agent condition
get '/', :agent => /iPhone/ do
  # Mobile version
end

# Custom conditions
set(:auth) do |role|
  condition do
    unless current_user && current_user.has_role?(role)
      halt 403
    end
  end
end

get '/admin', :auth => :admin do
  # Only accessible to admins
end

# Host-based routing
get '/', :host => 'admin.example.com' do
  # Admin subdomain
end

Content Negotiation:

get '/users/:id', :provides => [:json, :xml, :html] do
  user = User.find(params[:id])

  case request.accept.first.to_s
  when 'application/json'
    json user.to_json
  when 'application/xml'
    xml user.to_xml
  else
    erb :user, locals: { user: user }
  end
end

# Or using provides helper
get '/users/:id' do
  user = User.find(params[:id])

  respond_to do |format|
    format.json { json user.to_json }
    format.xml { xml user.to_xml }
    format.html { erb :user, locals: { user: user } }
  end
end

Middleware Composition

Custom Middleware:

class RequestLogger
  def initialize(app)
    @app = app
  end

  def call(env)
    start_time = Time.now
    status, headers, body = @app.call(env)
    duration = Time.now - start_time

    puts "#{env['REQUEST_METHOD']} #{env['PATH_INFO']} - #{status} (#{duration}s)"

    [status, headers, body]
  end
end

use RequestLogger

Middleware Ordering:

# config.ru
use Rack::Deflater                    # Compression first
use Rack::Static                       # Static files
use Rack::CommonLogger                 # Logging
use Rack::Session::Cookie             # Sessions
use Rack::Protection                   # Security
use CustomAuthentication              # Auth
run Application

Template Integration

ERB Templates:

# views/layout.erb
<!DOCTYPE html>
<html>
<head>
  <title><%= @title || 'My App' %></title>
</head>
<body>
  <%= yield %>
</body>
</html>

# views/users/index.erb
<h1>Users</h1>
<ul>
  <% @users.each do |user| %>
    <li><%= user.name %></li>
  <% end %>
</ul>

# Controller
get '/users' do
  @users = User.all
  @title = 'User List'
  erb :'users/index'
end

Inline Templates:

get '/' do
  erb :index
end

__END__

@@layout
<!DOCTYPE html>
<html>
  <body><%= yield %></body>
</html>

@@index
<h1>Welcome</h1>

Template Engines:

# Haml
get '/' do
  haml :index
end

# Slim
get '/' do
  slim :index
end

# Liquid (safe for user content)
get '/' do
  liquid :index, locals: { user: current_user }
end

Error Handling Patterns

Comprehensive Error Handling:

class Application < Sinatra::Base
  # Development configuration
  configure :development do
    set :show_exceptions, :after_handler
    set :dump_errors, true
  end

  # Production configuration
  configure :production do
    set :show_exceptions, false
    set :dump_errors, false
  end

  # Specific exception handlers
  error ActiveRecord::RecordNotFound do
    status 404
    json({ error: 'Resource not found' })
  end

  error ActiveRecord::RecordInvalid do
    status 422
    json({ error: 'Validation failed', details: env['sinatra.error'].message })
  end

  error Sequel::NoMatchingRow do
    status 404
    json({ error: 'Not found' })
  end

  # HTTP status handlers
  not_found do
    json({ error: 'Endpoint not found' })
  end

  error 401 do
    json({ error: 'Unauthorized' })
  end

  error 403 do
    json({ error: 'Forbidden' })
  end

  error 422 do
    json({ error: 'Unprocessable entity' })
  end

  # Catch-all error handler
  error do
    error = env['sinatra.error']
    logger.error("Error: #{error.message}")
    logger.error(error.backtrace.join("\n"))

    status 500
    json({ error: 'Internal server error' })
  end
end

Before/After Filters

Request Filters:

# Global before filter
before do
  content_type :json
end

# Path-specific filters
before '/admin/*' do
  authenticate_admin!
end

# Conditional filters
before do
  pass unless request.path.start_with?('/api')
  authenticate_api_user!
end

# After filters
after do
  # Add CORS headers
  headers 'Access-Control-Allow-Origin' => '*'
end

# Modify response
after do
  response.body = response.body.map(&:upcase) if params[:uppercase]
end

Session Management

Cookie Sessions:

use Rack::Session::Cookie,
  key: 'app.session',
  secret: ENV['SESSION_SECRET'],
  expire_after: 86400,  # 1 day
  secure: production?,
  httponly: true,
  same_site: :strict

helpers do
  def login(user)
    session[:user_id] = user.id
    session[:logged_in_at] = Time.now.to_i
  end

  def logout
    session.clear
  end

  def current_user
    return nil unless session[:user_id]
    @current_user ||= User.find_by(id: session[:user_id])
  end
end

Tier 3: Resources & Examples

Full Application Example

See assets/modular-app-template/ for complete modular application structure.

Performance Patterns

Caching:

# HTTP caching
get '/public/data' do
  cache_control :public, max_age: 3600
  etag calculate_etag
  last_modified last_update_time

  json PublicData.all.map(&:to_hash)
end

# Fragment caching with Redis
require 'redis'

helpers do
  def cache_fetch(key, expires_in: 300, &block)
    cached = REDIS.get(key)
    return JSON.parse(cached) if cached

    data = block.call
    REDIS.setex(key, expires_in, data.to_json)
    data
  end
end

get '/expensive-data' do
  data = cache_fetch('expensive-data', expires_in: 600) do
    perform_expensive_query
  end

  json data
end

Streaming Responses:

# Stream large responses
get '/large-export' do
  stream do |out|
    User.find_each do |user|
      out << user.to_csv_row
    end
  end
end

# Server-Sent Events
get '/events', provides: 'text/event-stream' do
  stream :keep_open do |out|
    EventSource.subscribe do |event|
      out << "data: #{event.to_json}\n\n"
    end
  end
end

Production Configuration

Complete config.ru:

# config.ru
require_relative 'config/environment'

# Production middleware
if ENV['RACK_ENV'] == 'production'
  use Rack::SSL
  use Rack::Deflater
end

# Static files
use Rack::Static,
  urls: ['/css', '/js', '/images'],
  root: 'public',
  header_rules: [
    [:all, {'Cache-Control' => 'public, max-age=31536000'}]
  ]

# Logging
use Rack::CommonLogger

# Sessions
use Rack::Session::Cookie,
  secret: ENV['SESSION_SECRET'],
  same_site: :strict,
  httponly: true,
  secure: ENV['RACK_ENV'] == 'production'

# Security
use Rack::Protection,
  except: [:session_hijacking],
  use: :all

# Rate limiting (production)
if ENV['RACK_ENV'] == 'production'
  require 'rack/attack'
  use Rack::Attack
end

# Mount applications
map '/api/v1' do
  run ApiV1::Application
end

map '/' do
  run WebApplication
end

Rack::Attack Configuration:

# config/rack_attack.rb
class Rack::Attack
  # Throttle login attempts
  throttle('login/ip', limit: 5, period: 60) do |req|
    req.ip if req.path == '/login' && req.post?
  end

  # Throttle API requests
  throttle('api/ip', limit: 100, period: 60) do |req|
    req.ip if req.path.start_with?('/api')
  end

  # Block suspicious requests
  blocklist('block bad user agents') do |req|
    req.user_agent =~ /bad_bot/i
  end
end

Testing Patterns

See references/testing-examples.rb for comprehensive test patterns.

Project Structure

Recommended modular structure:

app/
  controllers/
    base_controller.rb
    api_controller.rb
    users_controller.rb
    posts_controller.rb
  models/
    user.rb
    post.rb
  services/
    user_service.rb
    authentication_service.rb
  helpers/
    application_helpers.rb
    view_helpers.rb
config/
  environment.rb
  database.yml
  puma.rb
db/
  migrations/
lib/
  middleware/
    custom_auth.rb
  tasks/
public/
  css/
  js/
  images/
views/
  layout.erb
  users/
    index.erb
    show.erb
spec/
  controllers/
  models/
  spec_helper.rb
config.ru
Gemfile
Rakefile
README.md

Additional Resources

  • Routing Examples: assets/routing-examples.rb
  • Middleware Patterns: assets/middleware-patterns.rb
  • Modular App Template: assets/modular-app-template/
  • Production Config: references/production-config.rb
  • Testing Guide: references/testing-examples.rb