Overview

Getting started

Install#

# serve.nimble
requires "serve"

Or add it directly to an existing project's .nimble file alongside your other dependencies.

Your first server#

The simplest possible server is a function from Request to Response:

import serve
 
serve(8080, proc(req: Request): Response {.closure.} =
  ok("hello"))

Run it, then:

curl http://127.0.0.1:8080/
# hello

That's the whole contract: read req, return a Response. No middleware chain to configure, no framework runtime underneath.

Serving static files#

Drop in the built-in static handler instead of writing your own:

import serve
 
serve(8080, staticHandler("./public"))

staticHandler includes path-traversal safety by default — a request for ../../etc/passwd is rejected before it touches the filesystem.

What you get for free#

Every server built on serve, sync or async, inherits the same protections without extra configuration:

Keep-alive

Connections stay open across requests by default, following HTTP/1.1 semantics.

Request-size caps

Oversized request bodies are rejected with 413 before they're fully buffered.

Streamed responses

Response bodies stream to the client — no truncation on large payloads.

Slowloris guard

Connections that trickle bytes in slowly to exhaust server resources are detected and closed.

Next step#

The example above is synchronous — one request at a time. For concurrent connections, TLS, HTTP/2 or HTTP/3, move to the reactor, the async scheduler the rest of this stack rides on.