Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Writing the response

What a route may return, and the helpers that build it: render, redirect, halt, json, status.

A route block returns its response. There is no render-and-return-nil, no implicit template lookup, and no response object to mutate.

What a block may return

ReturnBecomes
String200 text/html; charset=utf-8 with that body
render :name, ...a String (the rendered page) — same as above
json(value) / text(s) / html(s)a Melee::Response with that content type
Melee::Response.stream { |out| ... }a chunked response
anything elseto_s, sent as HTML

redirect and halt do not return: they unwind the block immediately.

get "/notes" do
  render :notes, notes: db.query("SELECT id, text FROM notes ORDER BY id")
end

status and header

get "/feed.xml" do
  status 200
  header "Cache-Control", "no-store"
  text db.first("SELECT body FROM feed")["body"].to_s
end

status(Integer) and header(name, value) set the status and headers of the response the block is about to return. Both stringify the value. They apply to a returned String, render, json, text and html. They do not apply to redirect or halt, which build their own response and ignore anything set earlier. A redirect therefore carries only Location and Content-Length.

json, text, html

get "/api/notes" do
  json({ "notes" => db.query("SELECT id, text FROM notes") })
end
CallReturnsContent-Type
json(value)Melee::Responseapplication/json (JSON.generate(value))
text(string)Melee::Responsetext/plain; charset=utf-8
html(string)Melee::Responsetext/html; charset=utf-8

json accepts Hashes, Arrays, Strings, Integers, Floats, true, false, nil. Not your own classes: build a Hash first.

All three take an optional type: to override the content type, for the formats that are text under another name:

get "/card.vcf" do
  text build_vcard, type: "text/vcard; charset=utf-8"
end

redirect

post "/notes" do
  db.run "INSERT INTO notes (text) VALUES (?)", params.fetch(:text)
  redirect "/notes"
end

redirect(to) sends 303 See Other with a Location header and an empty body. Always 303, for every method. redirect back uses the Referer header, falling back to "/"back is a plain method returning a String.

halt

get "/d/:token" do
  halt 404 unless secure_equal?(params.fetch(:token), setting("display_token"))
  render :display, layout: false
end

post "/notes" do
  halt 400, "Date must be YYYY-MM-DD" unless params.fetch(:on_date).match?(/\A\d{4}-\d\d-\d\d\z/)
  db.run "INSERT INTO notes (on_date, text) VALUES (?, ?)", params.fetch(:on_date), params.fetch(:text)
  redirect "/notes"
end

halt(status = 200, body = "", type: nil) stops the request with a text/plain response carrying body, or type: if you give one. It works inside a route, inside a before filter, and inside any method a route calls. It discards a status or header set earlier in the request.

Streaming

get "/export.csv" do
  Melee::Response.stream(content_type: "text/csv; charset=utf-8",
                         headers: { "Content-Disposition" => "attachment; filename=notes.csv" }) do |out|
    out << "id,text\n"
    db.query("SELECT id, text FROM notes").each { |r| out << "#{r["id"]},#{r["text"]}\n" }
  end
end

Melee::Response.stream(content_type: "text/plain; charset=utf-8", status: 200, headers: {}) { |out| ... } returns a Melee::Response. out accepts << with a String and nothing else. The headers set with header are not merged into a streamed response — pass them in the headers: keyword instead. An exception raised inside the block ends the response early and is logged; the client has already had a 200. Under melee dev the whole stream is buffered before anything is sent.

Errors

An exception that escapes the block is logged and turned into a 500 — either your error handler’s output or Something went wrong. See app.md.

What is not here

There is no send_file: static files belong in public/, which the server sends without waiting for the app. There is no content_type setter (use type: on text/html/json, or header "Content-Type", ...), no attachment, no last_modified/etag, and no cookie API beyond session — set one with header "Set-Cookie", ... if you must.