APIs return JSON; pages return HTML. nyx-serve's template engine
renders HTML with one safety default that matters:
{{value}} is HTML-escaped. You have to
opt out with {{{raw}}} — the dangerous thing is
the exception, not the default you forget.
import "nyx-serve/src/template" fn page_notes(req: Request) -> Response { var item1: Map = map_new() item1.insert("title", "Buy coffee") var item2: Map = map_new() item2.insert("title", "<script>alert(1)</script>") var items: Array = [item1, item2] var ctx: Map = map_new() ctx.insert("heading", "My notes") ctx.insert("has_items", "1") ctx.insert("items", items) let tmpl: String = "<h1>{{heading}}</h1> {{#if has_items}} <ul>{{#each items}}<li>{{title}}</li>{{/each}}</ul> {{else}}<p>No notes yet.</p>{{/if}}" return response_html(200, tpl_render(tmpl, ctx)) }
That second item — a <script> tag — renders as
escaped text, not a live tag. The template features:
{{key}} escaped, {{{key}}} raw{{#if key}}…{{else}}…{{/if}}{{#each key}}…{{/each}} over an Array<Map>, nestable{{> name}} partials, registered with tpl_partial(name, template)
Serve a directory under a URL prefix. The _cached
variant adds Cache-Control and Last-Modified
so browsers and CDNs can skip re-downloads.
import "nyx-serve/src/files" // in main(): app_static_cached(app, "/assets/", "public/", 3600)
A request to /assets/style.css now serves public/style.css with a one-hour cache.
Classic form fields land pre-parsed on req.form (you saw
this in step 3). File uploads use multipart_parse, which
is binary-safe — a PNG with NUL bytes comes through intact:
import "nyx-serve/src/multipart" fn upload(req: Request) -> Response { let ctype: String = http_find_header(req.headers_flat, "Content-Type") let parts: Array = multipart_parse(req.body, ctype) let p: Array = parts[0] let filename: String = part_filename(p) let data: String = part_value(p) return response_text(200, filename + ": " + int_to_string(data.length()) + " bytes") }
part_name,
part_filename, part_ctype,
part_value. The 4-slot layout behind them is internal.