Skip to content

The nilo guide

One page per thing you might want to do. Read them in order the first time — each one assumes the ones above it — and jump straight in afterwards.

Which module a page is about

nilo is a toolkit of eleven modules rather than one library, and which one a page belongs to is decided by a single question — does it need the event loop? (ADR 038, ADR 061)

Module What it is Pages
nilo_http the server: routing, handlers, middleware, files, sockets everything below except the ones named on the right
nilo_sql Postgres and SQLite: your struct is the table Talking to a database — nine pages
nilo_s3 object storage: your bucket is a type Object storage
nilo_fetch calling somebody else's HTTP API from a handler Calling somebody else's API
nilo_job work that runs later, again, or on a schedule: a queue in your database Work that runs later
nilo_config settings out of the environment, into a struct of yours Settings
nilo_pw password hashing: argon2id, stored as PHC Sessions
nilo_cache an expiring cache in this process, holding no pointers A cache in this process, and the Space that Answering once keeps its answers in
nilo_jwt checking somebody else's signed token: RS256, ES256 and a JWKS Checking somebody else's token
nilo_id UUIDs, v4 and v7 Identifiers
nilo_core Str, the Scope and the clock the rest share the reference

There is no module called nilo — the word names the project, and the server is nilo_http. Every example here writes the alias back, which is all it costs:

const nilo = @import("nilo_http");

Start here

  1. Getting started — install, the two lines of root wiring, and a server that answers.
  2. Handlers — the one rule that decides what every argument means, and what a return value turns into.
  3. Routing — patterns, why order doesn't matter, groups and plugins.

Handling a request

  1. Requests — path params, query structs, JSON bodies, and bodies too big to hold.
  2. Forms — an HTML form as a struct of yours, file uploads, and the binding that names the field that broke instead of refusing the lot.
  3. Responses — statuses, headers and redirects, and the Ctx layer underneath the typed one.
  4. Cookies — reading them, setting them, and the signed-in user.
  5. Sessions — a struct of yours, sealed into one cookie, with nothing kept on the server, and checking the password that opens one.
  6. Streaming — writing an answer whose length nobody knows yet, and server-sent events.
  7. WebSocket — a handler that doesn't return for a while.
  8. gRPC — a method as a route, on a listener of its own, in a build that asks for it.

Building an application

  1. Middleware — the onion, and resolved values for the signed-in user.
  2. Services — shared state across threads, locks, and the rule about blocking calls.
  3. Static files — a directory held in memory, with ETags and range requests, and a file too big to hold opened per request.
  4. Errors — failing a request from anywhere, what a client is told, and request ids for tying a failure to its log line.
  5. Settings — nilo_config: the environment read into a struct of yours before anything opens, every bad one named at once, and the whole of a main that reads a .env on the way past.
  6. Work that is not a request — a summary written every minute or a cache warmed at startup: a fiber of your own, owned by the server, and the shutdown that reaches it. For work that is a row rather than a loop, nilo_job is below.
  7. Answering once — the Idempotency-Key header as a typed argument: a retry gets the kept answer back, the order is placed once, and the two refusals that keep a key honest.

The other modules

Each is one page, except the database, which is a folder. Every one says what the module is for, the whole of it in one example, every option with its default, what it answers instead of a value, what it costs, and what it will not do.

  1. Talking to a database — nilo_sql: your struct is the table, the query is a constant, and a misspelled column is a build error. Postgres and SQLite, written the same way. Nine pages, in order: tables, reading, writing, transactions, past one table, SQLite, making the tables and running it.
  2. Calling somebody else's API — nilo_fetch: one client for the whole program, a deadline on every call, a body that comes back in the request's own memory, and an Exchange for a body too big to hold.
  3. Object storage — nilo_s3: a bucket is a type, a key is not. Reading, writing, streaming an object through, and a presigned URL or POST form for a browser that talks to the bucket itself.
  4. Work that runs later — nilo_job: a job is a struct, the queue is a table in the database you already have, pushIn(&tx, …) commits with your rows, and a schedule declares what an overlap and a missed tick mean or it does not compile.
  5. A cache in this process — nilo_cache: a typed keyspace over one budget of memory, no pointer allowed in a value, a lookup that takes no lock, and a stats() that says why it is not hitting.
  6. Checking somebody else's token — nilo_jwt: a JWT an identity provider signed, verified in the order that is safe and read into a struct of yours; the signed-in user as a resolved value; what a key rotation looks like from here.
  7. Identifiers — nilo_id: a v7 for a key that sorts by when it was made, what it does and does not order, and why the randomness is an argument.

Shipping it

  1. Testing — handlers as ordinary functions, and the test client for the ones that write their answer.
  2. OpenAPI — an API document written from the signatures.
  3. Metrics — how many requests, at what statuses, how long; a Prometheus page in one call, and a counter of your own on it.
  4. Deploying — startup errors, panics, graceful shutdown, a health page the balancer can trust, tuning, and what isn't here yet.

Also