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

Putting it on a URL

So far the program runs when you ask it to. A trigger node makes it run when the outside world asks instead, and for an HTTP request that trigger is Route.

Replace main.weft with:

hello = Route -> (name: String) { path: "hello", method: "POST" }
answer = Reply { status: 201 }
answer.body = hello.name

Every POST to hello fires a fresh execution. The body’s name key comes out on the port you declared after the arrow, and Reply sends it back as the response, under a 201. No Rust, no node folder.

Two things in that program are worth a closer look.

The body keys are ports you declare. A route has no body port. You say which top-level keys you want (-> (name: String, age: Number)) and each one arrives typed on its own port, the same way an LLM call with Parse JSON on splits its reply. Anything you did not name is dropped, so name every key you mean to read. The request itself is always there on the fixed ports: method, path, params (the {name} captures of the path), query, headers, and caller (who the auth gate let in; null on an open route).

A port named like a {capture} in the path reads that capture (Route -> (id: String) { path: "cards/{id}" }). A port declared Image takes a picture the body carries as a data URL or base64: the route stores it and hands you the stored-file value, never the bytes.

Reply is one message. Behind a Route it is the response: status, headers, the body, and the exchange ends. The body’s shape follows the route’s dataType: any value on the default json, a String on text, a stored file on bytes. A stored file inside a json body goes out as a link the caller can fetch ({ url, mimeType, filename, sizeBytes }). Every answer mints a fresh link and the old one dies within minutes, so what you keep between runs is the file itself.

Turn it on

weft activate

activate compiles the project and registers it, printing activated <name> (<id>). A project with triggers has to be activated; one without them just runs.

It prints no URL, and it does not need to: the address is fixed by the install, so you can write it down before the program ever runs. Locally it is http://127.0.0.1:9999/connect/local/<your path>. That address answers on this machine only for now, so call it from here:

curl -X POST "http://127.0.0.1:9999/connect/local/hello" \
     -H "content-type: application/json" \
     -d '{"name":"ada"}'
"ada"

Each request is a full execution with its own color and its own row in the editor’s execution list. weft follow <project> streams them live as they arrive.

What just happened underneath

Activating the project told the runtime: when a request arrives at this path with this method, start an execution of this program and hand the held connection to whichever worker picks it up. Nothing in your program is listening.

So the endpoint exists whether or not any worker is running. When a request arrives cold the runtime starts one, which is why the first request after an idle period is slower. Workers shut themselves down after thirty seconds with nothing to do.

The response head is held until your program’s first outbound item: a Reply sets the status, a Stream starts a body, a Close ends it bare. A program that never does any of those holds the caller while it runs, and when the run ends the caller gets a 500 whose body says the run ended without answering. That is the program’s bug, and the 500 is how you find out.

The rest of the shapes an API takes (a route that answers 404, a stream of server-sent events, a WebSocket conversation, a route behind an API key) are in Building an API.

Next: putting a person in the loop.