{"id":20042699,"url":"https://github.com/webcpu/sinatrabook","last_synced_at":"2026-03-06T15:03:09.567Z","repository":{"id":72215794,"uuid":"158027659","full_name":"webcpu/SinatraBook","owner":"webcpu","description":null,"archived":false,"fork":false,"pushed_at":"2018-11-17T21:51:49.000Z","size":7,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-01-12T19:35:53.958Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/webcpu.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-11-17T21:47:12.000Z","updated_at":"2018-11-18T17:39:27.000Z","dependencies_parsed_at":"2023-06-26T01:50:50.211Z","dependency_job_id":null,"html_url":"https://github.com/webcpu/SinatraBook","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webcpu%2FSinatraBook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webcpu%2FSinatraBook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webcpu%2FSinatraBook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webcpu%2FSinatraBook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/webcpu","download_url":"https://codeload.github.com/webcpu/SinatraBook/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241470709,"owners_count":19968091,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-13T10:52:53.603Z","updated_at":"2026-03-06T15:03:09.555Z","avatar_url":"https://github.com/webcpu.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Introduction\n## What\nSinatra is a domain-specific language for building websites, web services, and web applications in Ruby. It emphasizes a minimalistic approach to development, offering only what is essential to handle HTTP requests and deliver responses to clients.\n## Example\nThe first Sinatra application\n```\nrequire 'sinatra'\nget '/' do \n  \"Hello, world!\" \nend\n```\nRun\n```\nruby -rubygems server.rb\n```\nAccess\n[http://localhost:4567/](http://localhost:4567/.)\n# Foundamentals\n## Routing\nThe core of any Sinatra application is the ability to respond to one or more routes. Routes are the primary way that users interact with your application.\n### Hypertext Transfer Protocol\nHTTP Message\nStart line, Headers, Message body\n### Verbs\nGET request is used to ask a server to return a representation of a resource.\nPOST is used to submit data to a web server.\nPUT is used to create or update a representation of a resource on a server.\nDELETE is used to destroy a resource on a server.\nPATCH is used to update a portion of a resource; this is in contrast to PUT, which replaces it wholesale.\n### Common Route Definition\n```\nrequire 'sinatra'\n\nget '/' do \n  \"Triggered via GET\"\nend\npost '/' do \n  \"Triggered via POST\" \nend\nput '/' do \n  \"Triggered via PUT\" \nend\ndelete '/' do \n  \"Triggered via DELETE\" \nend\npatch '/' do \n  \"Triggered via PATCH\" \nend\noptions '/' do \n  \"Triggered via OPTIONS\" \nend\n```\n### Many URLs, Similar Behaviors\n```\nrequire 'sinatra'\n['/one', '/two', '/three'].each do |route|\n  get route do\n    \"Triggered #{route} via GET\"\n  end\nend\n```\n### Routes with Parameters\nGet\n```\nrequire 'sinatra'\nget '/:name' do \n  \"Hello, #{params[:name]}!\" \nend\n```\nPost\n```\nrequire 'sinatra'\npost '/login' do\n  username = params[:username]\n  password = params[:password]\n  \"#{username}:#{password}\"\nend\n```\n### Routes with Query String Parameters\n```\nrequire 'sinatra'\nget '/:action' do\n  # assumes a URL in the form /action?name=XYZ\n  \"You asked for #{params[:action]} as well as #{params[:name]}\"\nend\n```\n### Routes with Wildcards\n```\nrequire 'sinatra'\nget '/*' do \n  \"You passed in #{params[:splat]}\" \nend\n```\n### Routes with Regular Expressions\n```\nrequire 'sinatra'\nget %r{/(sp|gr)eedy} do \n  \"You got caught in the greedy route!\" \nend\n```\n### Halting a Request\n```\nrequire 'sinatra'\nget '/halt' do\n  'You will not see this output.'\n  halt 500\nend\n\n```\n### Passing a Request\n```\nrequire 'sinatra'\nget %r{/(sp|gr)eedy} do\n  pass if request.path =~ /\\/speedy/\n  \"You got caught in the greedy route!\"\nend\nget '/speedy' do\n  \"You must have passed to me!\"\nend\n```\n### Redirecting a Request\n```\nrequire 'sinatra'\nget '/redirect' do\n  redirect 'http://www.google.com'\nend\nget '/redirect2' do\n  redirect 'http://www.google.com', 301\nend\n```\n## Static Files\n```\n#modify static directory 'public'\nset :pub lic_folder, File.dirname(__FILE__) + '/your_custom_location'\n```\n## Views\nInline Templates\nExternal View Files\n### Passing Data into Views\nmain.rb\n```\nrequire 'sinatra'\nget '/index' do\n  @name = 'Random User'\n  erb :index\nend\n```\n\nviews/index.erb\n```\n\u003c!DOCTYPE html\u003e\n\u003chtml\u003e\n  \u003chead\u003e\n    \u003cmeta charset=\"UTF-8\"\u003e\n    \u003ctitle\u003eExternal template\u003c/title\u003e\n  \u003c/head\u003e\n  \u003cbody\u003e\n    \u003ch1\u003e\u003c%= @name %\u003e Worked!\u003c/h1\u003e\n  \u003c/body\u003e\n\u003c/html\u003e\n```\n## Filters\nSinatra supports filters as a way to modify requests and responses, both before and after a route has been executed.\n```\nrequire 'sinatra'\nbefore do\n  @before_value = 'foo'\nend\nget '/' do\n  \"before_value has been set to #{@before_value}\"\nend\nafter do\n  puts \"After filter called to perform some task.\"\nend\n```\n## Handling Errors\n### 404 Not Found\n```\nrequire 'sinatra'\nbefore do \n  content_type :txt \nend\nnot_found do\n  \"Whoops! You requested a route that wasn't available.\"\nend\n```\n### 500 Internal Server Error\n```\nrequire 'sinatra'\nconfigure do\n  set :show_exceptions, false\nend\n\nget '/div_by_zero' do\n  0 / 0\n  \"You won't see me.\"\nend\n\nerror do\n  \"NOT WORKING?\"\nend\n```\n## Configuration\n```\nrequire 'sinatra'\n\nconfigure do\n  mime_type :plain, 'text/plain'\nend\n\nbefore '/plain-text' do\n  content_type :txt\nend\n\nget '/html' do\n  content_type :html\n  '\u003ch1\u003eYou should see HTML rendered.\u003c/h1\u003e'\nend\n\nget '/plain-text' do\n  '\u003ch1\u003eYou should see plain text rendered.\u003c/h1\u003e'\nend\n```\n## HTTP Headers\n```\nrequire 'sinatra'\n\nbefore do\n  content_type :txt\nend\n\nget '/' do\n  headers \"X-Custom-Value\" =\u003e \"This is a custom HTTP header.\" 'Custom header set'\nend\n\nget '/multiple' do\n  headers \"X-Custom-Value\" =\u003e \"foo\", \"X-Custom-Value-2\" =\u003e \"bar\" 'Multiple custom headers set'\nend\n```\n## Exploring the request Object\n```\nrequire 'sinatra'\n\nbefore do\n  content_type :txt\nend\n\nget '/' do\n  request.env.map { |e| e.to_s + \"\\n\" }\nend\n```\n### Caching\n### Setting Headers Manually\n```\nrequire 'sinatra'\n\nbefore do\n  content_type :txt\nend\n\nget '/' do\n  t = Time.at(Time.now.to_i + (60 * 60)).to_s\n  headers \"Cache-Control\" =\u003e \"public, must-revalidate, max-age=3600\",\n          \"Expires\" =\u003e t\n  \"This page rendered at #{Time.now}.\"\nend\n```\n### Settings Headers via expires\n```\nrequire 'sinatra'\n\nbefore do\n  content_type :txt\nend\n\nget '/cache' do\n  expires 3600, :public, :must_revalidate\n  \"This page rendered at #{Time.now}.\"\nend\n```\n### ETags\nETags, short for entity tags, are another way to represent how fresh a resource is via HTTP. They are server-generated identifiers that are used to “fingerprint” a resource in a given state. A client can safely assume that if the ETag for a resource has changed, then the resource itself has changed and should be fetched from the server again.\n\n**Generating ETags**\n```\nrequire 'sinatra'\nrequire 'uuid'\n\nbefore do\n  content_type :txt\n  @guid = UUID.new.generate\nend\n\nget '/etag' do\n  etag @guid\n  \"This resource has an ETag value of #{@guid}.\"\nend\n```\n\n**Weak ETags**\n```\nrequire 'sinatra'\nrequire 'uuid'\nbefore do\n  content_type :txt\n  @guid = UUID.new.generate\nend\nget '/etag' do\n  etag @guid, :weak\n  \"This resource has an ETag value of #{@guid}.\"\nend\n```\n## Sessions\nAlthough HTTP is itself a stateless protocol, one way to maintain the state for a user is through the use of cookie-based sessions. In this approach, a cookie (in this case, one named rack.session) is stored client-side and used to house data related to the activity in the current user’s session.\n### Setting/Fetching a Session\n\n```\nrequire 'sinatra'\n\nconfigure do\n  enable :sessions\nend\n\nbefore do\n  content_type :txt\nend\n\nget '/set' do\n  session[:foo] = Time.now\n  \"Session value set.\"\nend\n\nget '/fetch' do\n  \"Session value: #{session[:foo]}\"\nend\n```\n### Destroying a Session\n\n```\nrequire 'sinatra'\n\nconfigure do\n  enable :sessions\nend\n\nbefore do\n  content_type :txt\nend\n\nget '/set' do\n  session[:foo] = Time.now\n  \"Session value set.\"\nend\n\nget '/fetch' do\n  \"Session value: #{session[:foo]}\"\nend\n\nget '/logout' do\n  session.clear\n  redirect '/fetch'\nend\n```\n## Cookies\nCookies are small amounts of metadata stored client-side. There are essentially two types of cookies: session and persistent. They differ by the points at which they expire; session cookies are destroyed when the user closes his browser (or otherwise ends his session), and persistent cookies expire at a predetermined time that is stored with the cookie itself.\n\n```\nrequire 'sinatra'\n\nget '/' do\n  response.set_cookie \"foo\", \"bar\"\n  \"Cookie set. Would you like to \u003ca href='/read'\u003eread it\u003c/a\u003e?\"\nend\n\nget '/read' do\n  \"Cookie has a value of: #{request.cookies['foo']}.\"\nend\n\nget '/delete' do\n  response.delete_cookie \"foo\"\n  \"Cookie has been deleted.\"\nend\n```\n## Attachments\n```\nrequire 'sinatra'\n\nbefore do\n  content_type :txt\nend\n\nget '/attachment' do\n  attachment 'story.txt'\n  \"Here's what will be sent downstream, in an attachment called 'story.txt'.\"\nend\n```\n## Streaming\nThe streaming support abstracts away the differences in different Rack-compatible servers, leaving you to focus solely on developing the functionality you desire as opposed to the plumbing that makes it possible.\n\n```\nrequire 'sinatra'\n\nbefore do\n  content_type :txt\nend\n\nget '/consume' do\n  stream do |out|\n    out \u003c\u003c \"Wanna hear a joke about potassium?\\n\"\n    sleep 1.5\n    out \u003c\u003c \"K.\\n\"\n    sleep 1.5\n    out \u003c\u003c \"I also have one about sodium!\\n\"\n    sleep 1.5\n    out \u003c\u003c \"Na.\\n\"\n  end\nend\n```\n# How it works\nOnce you understand what is going on backstage, it becomes significantly easier to take full advantage of the available API (or even extend it).\n## The Inner Self\n```\nrequire 'sinatra'\n\nouter_self = self\nget '/' do\n  content_type :txt\n  \"outer self: #{outer_self}, inner self: #{self}\"\nend\n```\n\n```\nouter self: main, inner self: #\u003cSinatra::Application:0x00007fbd188ae9e0\u003e\n```\n## Helpers and Extensions\n### Creating Sinatra Extensions\nDefine an extension that can handle both GET and POST requests to a particular URL\npost_get.rb\n```\nrequire 'sinatra/base'\n\nmodule Sinatra\n  module PostGet\n    def post_get(route, \u0026block)\n      get(route, \u0026block)\n      post(route, \u0026block)\n    end\n  end\n  register PostGet\nend\n```\nserver.rb\n```\nrequire 'sinatra'\nrequire './post_get'\n\npost_get '/' do\n  \"Hi #{params[:name]}\"\nend\n```\n### Helpers\nHelpers and extensions are something like cousins: you can recognize them both as being from the same family, but they have quite different roles to play. Instead of calling register to let Sinatra know about them, you pass them to helpers. Most importantly, they’re available both in the block you pass to your route and the view template itself, making them effective across application tiers.\n\nlink_helper.rb\n```\nrequire 'sinatra/base'\n\nmodule Sinatra\n  module LinkHelper\n    def link(name)\n      case name\n      when :about then '/about'\n      when :index then '/index'\n      else \"/page/#{name}\"\n      end\n    end\n  end\n  helpers LinkHelper\nend\n```\nserver.rb\n```\nrequire 'sinatra'\nrequire './link_helper'\n\nget '/' do\n  erb :helper\nend\n```\n./views/helper.erb\n```\n\u003chtml\u003e \n\u003chead\u003e \n\u003ctitle\u003eLink Helper Test\u003c/title\u003e \n\u003c/head\u003e \n\u003cbody\u003e \n\u003cnav\u003e \n\u003cul\u003e \n\u003cli\u003e\u003ca href=\"\u003c%= link(:index) %\u003e\"\u003eIndex\u003c/a\u003e\u003c/li\u003e \n\u003cli\u003e\u003ca href=\"\u003c%= link(:about) %\u003e\"\u003eAbout\u003c/a\u003e\u003c/li\u003e \n\u003cli\u003e\u003ca href=\"\u003c%= link(:random) %\u003e\"\u003eRandom\u003c/a\u003e\u003c/li\u003e \n\u003c/ul\u003e\n\u003c/nav\u003e \n\u003c/body\u003e \n\u003c/html\u003e\n```\n### Helpers Without Modules\nSometimes you need to create a helper or two that are only going to be used in one application or for a specific purpose.\n```\nrequire 'sinatra'\n\nhelpers do\n  def link(name)\n    case name\n    when :about then '/about'\n    when :index then '/index'\n    else \"/page/#{name}\"\n    end\n  end\nend\n\nget '/' do\n  erb :index\nend\n\nget 'index.html' do\n  redirect link(:index)\nend\n\n__END__\n\n@@index\n\u003ca href=\"\u003c%= link :about %\u003e\"\u003eabout\u003c/a\u003e\n```\n## Request and Response\nThe next step in understanding Sinatra’s internals is to examine the flow of a request, from parsing to delivery of a response back to the client.\n### Rack\nIt’s an extremely simple protocol specifying how an HTTP server (such as Thin, which we’ve used throughout the book) interfaces with an application object, like Sinatra::Application, without having to know anything about Sinatra in particular.\n### Sinatra Without Sinatra\n```\nmodule MySinatra\n  class Application\n\n    def self.call(env)\n      new.call(env)\n    end\n\n    def call(env)\n      headers = {'Content-Type' =\u003e 'text/html'}\n      if env['PATH_INFO'] == '/'\n        status, body = 200, 'hi'\n      else\n        status, body = 404, \"Sinatra doesn't know this ditty!\"\n\n      end\n\n      headers['Content-Length'] = body.length.to_s\n      [status, headers, [body]]\n    end\n  end\nend\n\nrequire 'thin'\nThin::Server.start MySinatra::Application, 4567\n```\n## Rack It Up\nThe Rack specification supports chaining filters and routers in front of your application. In Rack slang, those are called middleware. This middleware also implements the Rack specification; it responds to call and returns an array as described above. Instead of simply creating that array on its own, it will use different Rack endpoint or middleware and simply call call on that object. Now this middleware can modify the request (the env hash), modify the response, decide whether or not to call the next endpoint, or any combination of those.\n### Middleware\nRack has an additional specification for middleware. Middleware is created by a factory object. This object has to respond to new; new takes at least one argument, which is the endpoint that will be wrapped by the middleware. Finally, the middleware returns the wrapped endpoint.\nconfig.ru\n```\nMyApp = proc do |env|\n  [200, {'Content-Type' =\u003e 'text/plain'}, ['ok']]\nend\n\nclass MyMiddleware\n  def initialize(app)\n    @app = app\n  end\n\n  def call(env)\n    if env['PATH_INFO'] == '/'\n      @app.call(env)\n    else\n      [404, {'Content-Type' =\u003e 'text/plain'}, ['not ok']]\n    end\n  end\nend\n\n# this is the actual configuration\nuse MyMiddleware\nrun MyApp\n```\nRun\n```\nrackup -p 4567 -s thin\n```\n### Sinatra and Middleware\nyou can use any Sinatra application as middleware.\n```\nrequire 'sinatra' \nrequire 'rack'\n\n# A handy middleware that ships with Rack \n# and sets the X-Runtime header \nuse Rack::Runtime\n\nget('/') { 'Hello world!' }\n```\n\nThe class, Sinatra::Application, is the factory creating the configured middleware instance (which is your application instance). When the request comes in, all before filters are triggered. Then, if a route matches, the corresponding block will be executed. If no route matches, the request is handed off to the wrapped application. The after filters are executed after we’ve got a response back from the route or wrapped app. Thus, your application is Rack middleware.\n## Dispatching\nSinatra relies on the “one instance per request” principle. However, when running as middleware, all requests will use the same instance over and over again. Sinatra performs a clever trick here: instead of executing the logic right away, it duplicates the current instance and hands responsibility on to the duplicate instead. Since instance creation (especially with all the middleware being set up internally) is not free from a performance and resources standpoint, it uses that trick for all requests (even if running as endpoint) by keeping a prototype object around.\n# Modular Applications\nNormal Sinatra applications actually live in Sinatra::Appli cation, which is a subclass of Sinatra::Base. If we do, we usually don’t use Sinatra::Application, but instead we create our own subclass of Sinatra::Base. This style is called a modular application, as opposed to classic applications that are using the Top Level DSL.\n\nBut why would one want to use modular style? If you activate the Top Level DSL (by requiring sinatra), Sinatra extends the Object class, somewhat polluting the global namespace.\n## Subclassing Sinatra\nCreating your own Subclass\n```\nrequire \"sinatra/base\"\n\nclass MyApp \u003c Sinatra::Base\n  get '/' do\n    \"Hello from MyApp!\"\n  end\nend\n```\nRoutes outside of the class body\n```\nrequire \"sinatra/base\"\n\nclass MyApp \u003c Sinatra::Base; end\nMyApp.get '/' do \n  \"Hello from MyApp!\" \nend\n```\n### Running Modular Applications\nServing a modular application with run!\n```\nrequire \"sinatra/base\"\n\nclass MyApp \u003c Sinatra::Base\n  get '/' do\n    \"Hello from MyApp!\"\n  end\n  run!\nend\n```\n\nOnly start a server if the file has been executed directly\nmy_app.rb\n```\nrequire \"sinatra/base\"\n\nclass MyApp \u003c Sinatra::Base\n  get '/' do\n    \"Hello from MyApp!\"\n  end\n\n  # $0 is the executed file\n  # __FILE__ is the current file\n  run! if __FILE__ == $0\nend\n```\nconfig.ru\n```\nrequire \"./my_app\" \nrun MyApp\n```\nRun with rackup\n```\nrackup -s thin -p 4567\n```\n## About Settings\nReading and writing settings\n```\nrequire 'sinatra'\n\nset :title, \"My Website\"\n\n# configure let's you specify env dependent options\nconfigure :development, :test do\n  enable :admin_access\nend\n\nif settings.admin_access?\n  get('/admin') { 'welcome to the admin area' }\nend\n\nget '/' do\n  \"\u003ch1\u003e#{ settings.title }\u003c/h1\u003e\"\nend\n```\n### Settings and Classes\nAnother short code survey reveals that settings is actually just an alias for the current application class. Moreover, settings is available both as class and as instance method\n\n```\n# Access settings defined with Base.set. \ndef self.settings \n  self \nend\n\n# Access settings defined with Base.set. \ndef settings \n  self.class.settings \nend\n```\n## Subclassing Subclasses\n### Route Inheritance\nInherited routes\n``` \nrequire 'sinatra/base'\n\nclass GeneralApp \u003c Sinatra::Base \n  get '/about' do \n    \"this is a general app\" \n  end\nend\n\nclass CustomApp \u003c GeneralApp \n  get '/about' do \n    \"this is a custom app\" \n  end\nend\n\n# This route will also be available in CustomApp \nGeneralApp.get '/' do \n    \"\u003ca href='/about'\u003emore infos\u003c/a\u003e\" \nend\n\nCustomApp.run!\n``` \n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebcpu%2Fsinatrabook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebcpu%2Fsinatrabook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebcpu%2Fsinatrabook/lists"}