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¶
- Getting started — install, the two lines of root wiring, and a server that answers.
- Handlers — the one rule that decides what every argument means, and what a return value turns into.
- Routing — patterns, why order doesn't matter, groups and plugins.
Handling a request¶
- Requests — path params, query structs, JSON bodies, and bodies too big to hold.
- 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.
- Responses — statuses, headers and redirects, and the
Ctxlayer underneath the typed one. - Cookies — reading them, setting them, and the signed-in user.
- Sessions — a struct of yours, sealed into one cookie, with nothing kept on the server, and checking the password that opens one.
- Streaming — writing an answer whose length nobody knows yet, and server-sent events.
- WebSocket — a handler that doesn't return for a while.
- gRPC — a method as a route, on a listener of its own, in a build that asks for it.
Building an application¶
- Middleware — the onion, and resolved values for the signed-in user.
- Services — shared state across threads, locks, and the rule about blocking calls.
- Static files — a directory held in memory, with ETags and range requests, and a file too big to hold opened per request.
- Errors — failing a request from anywhere, what a client is told, and request ids for tying a failure to its log line.
- Settings —
nilo_config: the environment read into a struct of yours before anything opens, every bad one named at once, and the whole of amainthat reads a.envon the way past. - 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_jobis below. - Answering once — the
Idempotency-Keyheader 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.
- 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. - 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 anExchangefor a body too big to hold. - 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. - 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. - 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 astats()that says why it is not hitting. - 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. - 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¶
- Testing — handlers as ordinary functions, and the test client for the ones that write their answer.
- OpenAPI — an API document written from the signatures.
- Metrics — how many requests, at what statuses, how long; a Prometheus page in one call, and a counter of your own on it.
- Deploying — startup errors, panics, graceful shutdown, a health page the balancer can trust, tuning, and what isn't here yet.
Also¶
- The reference — the whole surface as a list.
../adr/— why each decision went the way it did.../roadmap.md— what's next.../decided.md— what's refused, and why.../../CONTEXT.md— the project's vocabulary.