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

1. A new app

melee new, the nine files it writes, and the development server.

melee new notes
cd notes
created ./notes

The name has to be lowercase letters, digits and dashes, not starting with a dash, at most 40 characters — the same rule the server applies, so a name that scaffolds is a name that deploys. melee new refuses a directory that already exists and is not empty.

What it wrote

notes/
  melee.toml             which server, and what URL the app will answer at
  spin.toml              the compiler's manifest: this app depends on the melee library
  app.rb                 your routes
  views/layout.erb       the page wrapper
  views/index.erb        the one page
  public/app.css         served straight from disk at /app.css
  db/migrations/001_init.sql
  .meleeignore           paths melee push should not send
  README.md

Nine files, and you will edit four of them. Two of the others are worth a look now.

melee.toml is the only configuration:

name = "notes"
server = "http://127.0.0.1:7070"
url = "http://notes.localhost:8080"

spin.toml tells the compiler that this app is a package which depends on the melee library. You will not normally touch it:

[package]
name = "notes"
version = "0.1.0"

[dependencies]
melee = { path = "/path/to/melee/stdlib" }

And app.rb is a working two-route app:

# frozen_string_literal: true
title "New App"

get "/" do
  render :index, greeting: greeting
end

post "/greeting" do
  setting "greeting", params.fetch(:greeting)
  redirect "/"
end

def greeting = setting("greeting") || "World"

Three things to notice, because they are true of every melee file:

  • # frozen_string_literal: true at the top. String literals are frozen in this dialect. Build strings with +"" and << rather than mutating a literal.
  • greeting is a plain top-level method and the route calls it with no receiver. That works because a request is handled by a process that handles nothing else, so there is no instance to hang it off and nothing to thread through.
  • setting is a key/value store in the app’s database — handy for exactly this kind of “one value the app remembers”.

The scaffold also leaves a commented-out durable object at the bottom of app.rb. Delete it; step 5 writes a real one.

Run it

melee dev
melee dev: prepared 2 templates, 1 migrations
melee dev: http://127.0.0.1:4567 (Ctrl-C to stop)

Open http://127.0.0.1:4567. Type a name into the box, submit, and the page greets you.

Every request prints a line:

GET / -> 200 (4.5 ms)
GET / -> 200 (0.2 ms)
GET /app.css -> 200 (public/)
GET /nope -> 404 (0.0 ms)

melee dev runs your app under ordinary CRuby, not the compiled binary. That is what makes the loop fast, and it is also the one thing to keep in mind: CRuby will happily run Ruby that Spinel cannot compile. Step 6 runs the real compiler; until then, if something works here it might still not build.

There is no reload. Changing app.rb, a template or a migration means Ctrl-C and melee dev again. The database lives at .melee/app.sqlite inside the app directory — delete it to start over — and environment variables come from your shell.

Next

Routes and templates — turn the greeting into a notes board.