A web app with Sinatra
Installing Sinatra
Sinatra is a small framework: a route is a method call, and an app is a class. The gem pulls a handful of dependencies and one server adapter:
gem install sinatra
1 gem installed
The app in one file
Three routes cover the three shapes a JSON service needs: a plain text health answer, a path parameter and a JSON list. The class inherits from Sinatra::Base, which keeps the app a normal object a test can load:
require "sinatra/base"
require "json"
class App < Sinatra::Base
get "/" do
"Hello from Sinatra"
end
get "/hello/:name" do
"Hello, #{params[:name]}"
end
get "/api/todos" do
content_type :json
[{ id: 1, title: "write the lesson" }].to_json
end
end
App.run! if __FILE__ == $PROGRAM_NAME
params[:name] is the path segment. content_type :json sets the header before the body, and to_json comes from the json library. The guard at the bottom is the program-structure rule from lesson 86: the file runs the server only when it is the program, not when a test requires it.
Starting it
ruby sinatra_app.rb
[2026-11-05 00:28:41] INFO WEBrick 1.9.2 [2026-11-05 00:28:41] INFO ruby 3.4.12 (2026-10-23) [x86_64-linux-musl] == Sinatra (v4.2.1) has taken the stage on 4567 for development with backup from WEBrick [2026-11-05 00:28:41] INFO Rackup::Handler::WEBrick::Server#start: pid=593 port=4567
The development server listens on 4567. With Puma installed Sinatra chooses it instead; with neither, the rackup handler falls back to WEBrick, which is what the log above shows. The banner is a development message and not part of the protocol.
Three requests
A request per route, with the status, the content type and the body. The client is Net::HTTP from lesson 135 and lesson 136, so no separate tool is needed:
ruby -rnet/http -e '["/", "/hello/Ada", "/api/todos"].each { |p| r = Net::HTTP.get_response(URI("http://127.0.0.1:4567#{p}")); puts "#{r.code} #{r["content-type"]} #{r.body}" }'
200 text/html;charset=utf-8 Hello from Sinatra 200 text/html;charset=utf-8 Hello, Ada 200 application/json [{"id":1,"title":"write the lesson"}]
Sinatra defaults to text/html even for a plain string, which is why the first two lines say html. The JSON route sets the type explicitly, and the body is compact because to_json has no pretty-print by default.
What to watch for
The development server reloads code only with a reloader gem; a change to
the route needs a restart. That is the first difference from a Rails app,
and the reason a Sinatra project keeps the app small.
A route that raises returns a 500 with a stack trace in development and a
blank page in production. Add an error block for
the JSON shape and log the exception, or the caller gets HTML where it
expected JSON.
Next: Rails models without the whole app
Article author: Arthur Isaev