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