7 · Ship a full-stack app

Time to put it together: a notes app with login, server-rendered pages, and notes that persist in nyx-kv instead of a global Map. Same handler shapes you've already written — the only new thing is a few lines of RESP to talk to the database.

Talking to nyx-kv

nyx-kv speaks RESP, the Redis protocol. For GET/SET a tiny helper over a plain TCP socket is enough; for TLS, pipelines and the full command set use std/kvclient.

fn kv_get(key: String) -> String {
    let fd: int = tcp_connect("127.0.0.1", 6380)
    if fd < 0 { return "" }
    tcp_write(fd, "*2\r\n$3\r\nGET\r\n$" +
        int_to_string(key.length()) + "\r\n" + key + "\r\n")
    let header: String = tcp_read_line(fd)
    let n: int = string_to_int(header.substring(1, header.length()))
    var value: String = ""
    if n > 0 { value = tcp_read(fd, n) }
    tcp_close(fd)
    return value
}

Notes for a user are stored as a single pipe-joined string under notes:<user> — small and enough for the tutorial. Loading splits on |; saving appends and writes back.

The handlers

page_home shows a login form when there's no session, and the user's notes when there is. do_login starts a session and redirects home; do_create saves a note and redirects home. Every page is a template, escape-safe by default.

fn page_home(req: Request) -> Response {
    let user: String = session_get(req, "user")
    if user.length() == 0 {
        return response_html(200, tpl_render("{{> login_form}}", map_new()))
    }
    let titles: Array = load_notes(user)
    var items: Array = []
    var i: int = 0
    while i < titles.length() {
        let t: String = titles[i]
        var m: Map = map_new()
        m.insert("title", t)
        items.push(m)
        i = i + 1
    }
    var ctx: Map = map_new()
    ctx.insert("user", user)
    ctx.insert("items", items)
    let tmpl: String = "<h1>{{user}}'s notes</h1>
        <ul>{{#each items}}<li>{{title}}</li>{{/each}}</ul>{{> note_form}}"
    return response_html(200, tpl_render(tmpl, ctx))
}

The full source — helpers, both forms as partials, and the wiring in main() — is in the examples directory.

Ship it

nyx build produces one binary. Copy it to your server and run it under systemd — no runtime to install alongside it.

# /etc/systemd/system/notes-app.service
[Unit]
Description=notes-app
After=network-online.target

[Service]
ExecStart=/srv/notes-app/notes-app
Environment=PORT=3000
WorkingDirectory=/srv/notes-app
Restart=always

[Install]
WantedBy=multi-user.target
$ sudo systemctl enable --now notes-app

Put nyx-proxy in front for TLS termination and you have the exact shape that serves this very site.

The guardrails are already on. Oversized request bodies get an automatic 413 without being read; static responses carry ETags for conditional requests; the server is multi-threaded by default. You didn't configure any of it.

That's the whole framework — from curl … | sh to a deployed full-stack app. The API reference has every function with its exact signature when you need the details.