Sinatra & Rack
Reviewed & published by Brayan K
Rack is the simple interface every Ruby web framework speaks, and Sinatra is a tiny framework built on it — together they reveal exactly how web requests turn into responses in Ruby.
Part of the free Ruby course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
By the end of this lesson you'll write a bare Rack app, stack middleware, build routes with Sinatra, read params, and see why Rails itself is just a Rack application.
What You'll Learn in This Lesson
- The Rack interface: call(env) returns [status, headers, body]
- How middleware wraps an app
- Sinatra routes with get and friends
- Reading request data from params
- Rendering ERB views and using config.ru
- Why Rails is also a Rack app
1️⃣ The Rack Interface and Middleware
A Rack app is any object with a call(env) method that returns [status, headers, body]. That one contract is all a server needs to serve your app.
# Rack is THE interface every Ruby web framework speaks.
# A Rack app is any object with a call(env) method that
# returns [status, headers, body].
class HelloApp
def call(env)
status = 200
headers = { "Content-Type" => "text/plain" }
body = ["Hello from Rack!"] # body must respond to each
[status, headers, body] # the Rack response triple
end
end
# The env hash holds request info: PATH_INFO, REQUEST_METHOD, etc.
# Saved as config.ru, this is run with: rackup
# run HelloApp.newMiddleware wraps an app to add behavior — logging, auth, headers — by intercepting call.
# Middleware WRAPS an app to inspect or modify requests/responses.
class Logger
def initialize(app) # receives the next app in the stack
@app = app
end
def call(env)
puts "-> #{env["REQUEST_METHOD"]} #{env["PATH_INFO"]}"
status, headers, body = @app.call(env) # call the inner app
headers["X-Logged"] = "true" # tweak the response
[status, headers, body]
end
end
# In config.ru you stack middleware, then the app:
# use Logger
# run HelloApp.new
#
# Each request flows DOWN through middleware, then back UP.2️⃣ Building Routes with Sinatra
Sinatra hides Rack's boilerplate behind expressive route blocks like get "/" do ... end. Request data arrives in the params hash, and views render with ERB.
# Sinatra is a micro-framework that hides Rack's boilerplate.
require "sinatra"
get "/" do
"Welcome to Sinatra!" # the return value is the response body
end
get "/hello/:name" do
"Hello, #{params["name"]}!" # :name is captured into params
end
get "/search" do
"You searched for #{params["q"]}" # /search?q=ruby
end
# Render an ERB view from views/index.erb:
get "/page" do
erb :index
end
# Run it with: ruby app.rb (Sinatra boots its own server)Your turn. Finish the greeting route so it uses the captured :name parameter, then run it.
# 🎯 YOUR TURN — finish the route, then run: ruby app.rb
require "sinatra"
get "/" do
"Home page"
end
# Add a greeting route that uses the :name parameter.
get "/greet/:name" do
# 👉 TODO: return a string using params["name"]
"Hi there, #{params["name"]}!"
end
# ✅ Expected when visiting /greet/Ruby :
# Hi there, Ruby!Mini-Challenge: A Tiny Rack App
Write a raw Rack app in config.ru that returns different bodies for / and /about. Run with rackup.
# 🎯 MINI-CHALLENGE: a tiny Rack app from scratch.
# Save as config.ru and run with: rackup
#
# 1. Define a class with a call(env) method.
# 2. Return [200, {"Content-Type" => "text/plain"}, ["..."]] .
# 3. Use env["PATH_INFO"] to return a different message for "/about".
# 4. End config.ru with: run YourApp.new
#
# ✅ Example:
# curl localhost:9292/ -> Home
# curl localhost:9292/about -> About us
# your code hereCommon Errors (and the fix)
- Returning a string from call — Rack needs an array: [status, headers, body], where body responds to each.
- Body is not enumerable — Wrap it in an array: ["Hello"], not "Hello".
- Missing config.ru — rackup looks for a config.ru with a run line.
- Wrong params key type — Sinatra params keys are strings: params["name"].
- Middleware not calling the inner app — You must call @app.call(env) and return its result.
Pro Tips
- 💡 Peek under Rails: run rails middleware to see the Rack stack a Rails app uses.
- 💡 Rack::Test: a handy gem for testing Rack and Sinatra apps without a real server.
- 💡 Start small: Sinatra is perfect for webhooks, tiny APIs, and quick prototypes.
📋 Quick Reference — Sinatra & Rack
| Concept | Ruby Syntax |
|---|---|
| Rack app | def call(env); ...; end |
| Rack return | [200, headers, body] |
| Add middleware | use Logger |
| Sinatra route | get "/" do ... end |
| Read a param | params["id"] |
| Run a Rack app | rackup (config.ru) |
🎉 Lesson Complete!
- ✅ A Rack app responds to call(env)
- ✅ It returns [status, headers, body]
- ✅ Middleware wraps an app to add behavior
- ✅ Sinatra gives expressive routes and a params hash
- ✅ Rails itself is a Rack application
Practice quiz
What is Rack?
- a minimal interface between Ruby web servers and frameworks
- a database library
- a templating language
- a Ruby web server
Answer: a minimal interface between Ruby web servers and frameworks. Rack is a standard interface that connects Ruby web servers to web frameworks and applications.
A Rack application must respond to which method?
- handle
- run
- call
- serve
Answer: call. A Rack app is any object that responds to call(env).
What must a Rack app's call(env) return?
- a string of HTML
- a hash of params
- nothing
- an array of [status, headers, body]
Answer: an array of [status, headers, body]. call must return a three-element array: status code, headers hash, and an iterable body.
In the Rack return array, what is the first element?
- the request method
- the HTTP status code
- the response body
- the headers
Answer: the HTTP status code. The first element is the integer HTTP status code, such as 200.
What is Rack middleware?
- a database migration
- a CSS framework
- a test runner
- a layer that wraps an app to inspect or modify requests and responses
Answer: a layer that wraps an app to inspect or modify requests and responses. Middleware sits between the server and app, wrapping call to process requests and responses.
What is Sinatra?
- a lightweight Ruby web micro-framework
- a CSS toolkit
- a Ruby ORM
- a JavaScript library
Answer: a lightweight Ruby web micro-framework. Sinatra is a small, expressive web micro-framework built on top of Rack.
Which Sinatra code handles GET requests to the root path?
- route("/")
- get "/" do ... end
- path "/"
- when "/"
Answer: get "/" do ... end. get "/" do ... end defines a handler for GET requests to /.
In Sinatra, how do you read a URL query or form value named id?
- request.id
- args[:id]
- params["id"]
- query.id
Answer: params["id"]. Sinatra exposes request parameters through the params hash, e.g. params["id"].
What is the conventional file that tells Rack how to start an app?
- app.rb
- Rackfile
- server.yml
- config.ru
Answer: config.ru. config.ru is the rackup file that defines and runs the Rack application.
How does Ruby on Rails relate to Rack?
- Rails cannot use Rack
- Rack is built on Rails
- Rails is itself a Rack application
- Rails replaces Rack entirely
Answer: Rails is itself a Rack application. Rails is a Rack app, so it works with any Rack-compatible server and middleware.
Continue this course
- Previous: Testing with RSpec
- Next: Background Jobs with Active Job & Sidekiq — Move slow work off the request with Active Job, Sidekiq and Redis
- Quick reference: Ruby cheat sheet
Frequently asked questions
What is Rack and why does it matter?
Rack is a small but foundational specification that defines how Ruby web servers and web frameworks talk to each other. A Rack application is simply any Ruby object that responds to call(env) and returns a three-element array of [status, headers, body]. Because every major Ruby framework — Rails, Sinatra, Hanami — and every server — Puma, Unicorn, Passenger — agrees on this interface, you can mix and match them freely. Rack matters because it standardizes the request/response contract, enables reusable middleware, and means learning it once helps you understand how all Ruby web apps work under the hood.
What is Rack middleware?
Middleware is a layer that wraps a Rack application to inspect or modify requests on the way in and responses on the way out. A middleware is itself a Rack-like object: it is initialized with the next app in the stack and implements call(env), usually doing some work, calling the inner app, then doing more work with the result. Common examples include logging, authentication, session handling, compression, and error catching. You compose middleware in config.ru with use, stacking them so each request flows down through the layers to the app and the response flows back up — a clean way to add cross-cutting behavior.
When should I reach for Sinatra instead of Rails?
Sinatra is a micro-framework: it gives you expressive routing like get "/" do ... end, access to request data through params, and view rendering with ERB, all with very little ceremony. It shines for small services, APIs, prototypes, webhooks, and single-purpose apps where Rails would be overkill. Rails, by contrast, is a full-stack framework with conventions, an ORM, generators, and a huge ecosystem, which pays off for larger applications with many moving parts. A good rule of thumb: choose Sinatra when you want something tiny and explicit, and Rails when you want batteries-included structure for a growing app.
How is Rails related to Rack?
Rails is itself a Rack application. Underneath all its conventions and abstractions, a Rails app ultimately exposes an object that responds to call(env) and returns the standard Rack response triple, which is why it runs on any Rack-compatible server like Puma. Rails also uses the Rack middleware stack heavily: things like session management, cookies, parameter parsing, and security headers are implemented as middleware you can inspect with rails middleware. Understanding Rack therefore demystifies Rails — the magic at the edges is just the same call(env) contract that a bare Rack app or a Sinatra app uses.