5 · Templates, static & forms

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.

Rendering a page

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:

Static files

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.

Forms and uploads

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")
}
Always read parts through the accessors — part_name, part_filename, part_ctype, part_value. The 4-slot layout behind them is internal.