intermediate_ruby     about     articles

Sinatra Basics (Part 1)

How Done Is This Post? #

Anything I write ranges from “super rough notes-as-i-go, don’t even expect full sentences” to “this post is done and proven to deliver what it promises to deliver”

  1. Scratch-pad, take-notes-as-I-go <– we are here
  2. Review post, clean up for first ‘share’
  3. I mkdir new directory, follow my own steps, clarify
  4. Integrate feedback/answer questions from others
  5. Final cleanup, post is considered done unless someone reaches out with further questions

I want to build a Sinatra app that I can use to teach others about APIs, HTTP verbs, networking, and more.

It’s 2:45p, 01/22/21.

Taking notes as I go, because my primary “value add” in the world is learning things (albeit slowly) and helping others learn whatever I learned very very quickly.

Since me (1 person) learns slowly, but I can teach that same thing to 50 people quickly, I don’t have to feel bad when I broadcast to the world that I’m a slow learner. ;)

Lets start up a Sinatra app, from scratch. I’ll be taking notes how I usually do:

Minimum steps to get a running Sinatra app: #

MVP sinatra setup:

$ mkdir sinatra-starter
$ cd sinatra-starter
$ touch app.rb

Inside of app.rb add:

require 'sinatra'

get '/' do

Install sinatra if you have not: gem install sinatra

Start the Sinatra server with: ruby app.rb (Basically, tell ruby to run the file with the require statement, and the get '/' do) block…

Open up localhost:4567 and you should see:

sinatra running

Now, I want to receive and serve JSON, so I might as well start returning JSON.

How to do…

Oh, I just finished the first commit here:

Step 2 #

The app doesn’t recognize changes, so you either need to stop/restart the app to catch new code, or use Sinatra::Reloader

To stop the app, if you still have it running in the terminal, great, ctrl-c. If you closed the pane or cannot find the running process, find the process ID with

$ lsof -i :4567


ps aux | grep ruby

then kill the PID:

kill <pid>

and re-run with ruby app.rb

ooor… use rerun (an app), easier than what Sinatra’s docs recommend:

$ gem install rerun
$ rerun app.rb

super clean.

Step 3: Lets get JSON out of this thing #

I’m surprised to see that it looks like Sinatra doesn’t have any built-in JSON support.

OK. We’ll use the json gem, though this seems a little crusty. Surely not the sinatra way?

Here’s the gem: and of course check out the github page, though I didn’t get much from it:

commit 29a33fd7dbb2c231d67cfe4ede5c6f11f6d1b2ea (HEAD -> main)
Author: Josh Thompson <>
Date:   Fri Jan 22 15:33:40 2021 -0700

    now serving json

diff --git a/app.rb b/app.rb
index 7736302..d7f7f63 100644
--- a/app.rb
+++ b/app.rb
@@ -1,5 +1,6 @@
 require 'sinatra'
+require 'json'

 get '/' do
-  "hi"
+  JSON.generate(foo: "hi hello")

Step 4: Lets read AND return JSON #

Lets say I want to spiff this up just a tiny little level - instead of rendering a static result, lets render something about the request, just so folks can see something unique to them.

you could swap to JSON.generate(env) but the response is a bit wordy. Lets select what we want to get out.

Note: if you stick a pry in the get, like so:

require 'sinatra'
require 'json'

get '/' do
  require "pry"; binding.pry

and try to visit this in postman OR your browser, note that these are different:


Step 5: Lets figure out what matters in the request: #

Sticking a pry in the get "/" here’s my ENV:

=> {"SERVER_SOFTWARE"=>"thin 1.7.2 codename Bachmanity",
 "rack.version"=>[1, 0],
 "HTTP_ACCEPT_ENCODING"=>"gzip, deflate, br",
  #<Method: #<Thin::Connection:0x00007fe64c2b67b0 @signature=9, @request=#<Thin::Request:0x00007fe64c2b65d0 @parser=#<Thin::HttpParser:0x00007fe64c2b65a8>, @data=nil, @nparsed=374, @body=#<StringIO:0x00007fe64c2b6530>, @env={...}>, @response=#<Thin::Response:0x00007fe64c2b64e0 @headers=#<Thin::Headers:0x00007fe64c2b6468 @sent={}, @out=[]>, @status=200, @persistent=false, @skip_body=false>, @backend=#<Thin::Backends::TcpServer:0x00007fe64c0de140 @host="::1", @port=4567, @connections={70313548559320=>#<Thin::Connection:0x00007fe64c2b67b0 ...>}, @timeout=30, @persistent_connection_count=1, @maximum_connections=1024, @maximum_persistent_connections=100, @no_epoll=false, @ssl=nil, @threaded=true, @started_reactor=true, @server=#<Thin::Server:0x00007fe64c0ded98 @app=Sinatra::Application, @tag=nil, @backend=#<Thin::Backends::TcpServer:0x00007fe64c0de140 ...>, @setup_signals=true, @signal_queue=[], @signal_timer=#<EventMachine::PeriodicTimer:0x00007fe64b07e570 @interval=1, @code=#<Proc:0x00007fe64b07e5c0@/Users/joshthompson/.rvm/gems/ruby-2.5.8/gems/thin-1.7.2/lib/thin/server.rb:244>, @cancelled=false, @work=#<Method: EventMachine::PeriodicTimer#fire>>>, @stopping=false, @signature=2, @running=true>, @app=Sinatra::Application, @threaded=true, @can_persist=true, @idle=false>.post_process>,
   @default_formatter=#<Logger::Formatter:0x00007fe64c2ce8b0 @datetime_format=nil>,
   @logdev=#<Logger::LogDevice:0x00007fe64c2ce860 @dev=#<IO:<STDERR>>, @filename=nil, @mon_count=0, @mon_mutex=#<Thread::Mutex:0x00007fe64c2ce7c0>, @mon_owner=nil, @shift_age=nil, @shift_period_suffix=nil, @shift_size=nil>,
 "sinatra.route"=>"GET /"}

Lots to explore, but using Pry is kinda annoying.

Step X: I decided to spend a minute on the html/css #

Using new.css, and a bare-bones index.html, check out:

<!DOCTYPE html>
<html lang="en">
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Baby Yoda</title>
    <link rel="stylesheet" href="">
    <link rel="stylesheet" href="">

    Its a Starter!
    <h1>Hello, world. I'm a baby sinatra application</h1>

Next chapter, we’ll talk about:

Resources #