Routing
$app.routes.add, patterns, parameters and responding through $res.
$app.routes.add({
"/": func($req, $res) as void
$doc = new html_document
$doc.body.add(`<h2>Home</h2>`)
$res.document($doc)
end func
"/product/:id": func($req, $res) as void
$res.send("id: " + $req.params.id)
end func
"/old": "/pages/new.lx" # file route
})The type of the value decides what happens: a function is called, a string renders that .lx file. Both can appear in the same table.
If the route table is empty, the older behaviour applies: the single document added with $app.document.add($doc) is served at /. Once the table has entries, a path that matches nothing returns 404.
Patterns#
| Pattern | Matches | $req.params |
|---|---|---|
/ | / | — |
/login | /login, /login/ | — |
/product/:id | /product/42 | {id: "42"} |
/a/:x/b/:y | /a/1/b/2 | {x: "1", y: "2"} |
/files/*path | /files/a/b/c | {path: "a/b/c"} |
* may only appear at the end of a pattern, and captures the whole remaining path.
The order they are tried does not depend on the order they are written: patterns with more literal segments are tried first, and those containing * last. When /product/new and /product/:id are written together, which one comes first makes no difference.
Defining the same path twice is an error.
Parameters#
$req.params.id # error if absent
$req.param("id") # null if absent$res — the response#
| Call | Effect |
|---|---|
$res.document($doc) | serves a page: layout applied, scripts embedded, minify runs |
$res.send($value) | sets the body; an html_document is accepted too |
$res.json($value) | JSON body + application/json |
$res.status($code) | status code (100–599) |
$res.redirect($url) | 302 |
$res.redirect($url, 301) | permanent redirect (300–399) |
$res.header($name, $value) | response header |
$res.cookie($name, $value, {...}) | cookie |
document and send do the same work; the difference is intent. send sends a body (text, raw HTML, JSON); document serves a page. document accepts nothing but a document — raising an error is better than quietly returning a page with no layout.
If a handler finishes without calling send, document or redirect that is an error — it does not quietly return a blank page. A second send is an error too: HTTP has one response.
A newline in a redirect target is rejected; if it passed, a new header line could be injected into the response.
The context a handler runs in#
The page runs from the top on every request, and then the matching handler is called with that run's own context. This is why per-request modules, such as sessions, see the right instance:
$redis = new redis({host: "localhost", port: 6379}).connect()
session.redis = $redis
session.start() # every request, before the handlers
$app.routes.add({
"/": func($req, $res) as void
$res.send(session.get("user_id"))
end func
})Passing data to a partial#
An included file does not see the caller's variables. The way to pass a value is set_data, and the file sees it as $data:
"/about/:id": func($req, $res) as void
$about = new html_document
$about.set_data({id: $req.params.id})
$about = $about.from_file(path.pwd + "/about.lx")
$res.send($about.to_string())
end func# about.lx
$doc.body.add(`<p>id: ` + $data.id + `</p>`)Using $data when none was given is an error; it does not pass silently.