slug.web.routes
slug.web.routes — HTTP router and middleware
Pattern-based HTTP routing with composable middleware wrappers. Routes are registered on a Router and dispatched via handle.
Quick start
val { router, get, post, handle, withLog, withRecover } = import("slug.web.routes")
val { html, jsonOk, notFound } = import("slug.web.response")
val r = router()
/> get("/", fn(req) { html("<h1>Hello</h1>") })
/> get("/users/:id", fn(req) { jsonOk({ id: req.params["id"] }) })
/> post("/users", fn(req) { jsonOk(req.body) })
val app = r /> handle /> withLog /> withRecover
Route patterns
- Exact match:
"/users"matches only/users - Named param:
"/users/:id"captures the segment asreq.params["id"] - Wildcard mount:
"/static/*"matches any path starting with/static/and captures the remainder asreq.params["*"]
Middleware
Middleware wraps a handler function fn(req) -> Response and returns a new handler with the same signature. Chain middleware with />:
val app = fn(req) { handle(req, r) }
/> withLog
/> withRecover
/> withMaxBody(512_000)
/> withRequestId
Subrouters
Mount a child router at a prefix with mountRouter. The child router sees paths relative to the mount point:
val api = router()
/> get("/users", usersHandler)
/> get("/users/:id", userHandler)
val r = router()
/> mountRouter("/api", api)
Static files
val r = router()
/> mount("/static", static("public"))
TOC
get(nil)handle(request, r)head(r, pattern, handler)isRouter(x)mount(r, prefix, handler)mountRouter(r, prefix, childRouter)post(r, pattern, handler)router()static(dir, cacheTimeSeconds)subrouter(router)withHeader(nil)withLog(handler)withMaxBody(h, maxBytes)withRecover(h)withRequestId(handler, newRequestId)withTimeout(h, ms)withTraceContext(handler, newTraceId, newSpanId)
Functions
get(nil)
fn slug.web.routes#get(nil) -> @struct(Router)
registers a GET route on r and returns the updated router. nil
Examples
get({}, :k) // => nil
get({:k: 1}, :k) // => 1
handle(request, r)
fn slug.web.routes#handle(request, r) -> @struct(Response)
dispatches request through the router’s routes and returns a Response.
Returns a 404 Not Found response if no route matches. Route handlers receive the request with params populated from URL pattern segments.
| Parameter | Type | Default |
|---|---|---|
request | — | |
r | — |
head(r, pattern, handler)
fn slug.web.routes#head(r, @str pattern, @fn handler) -> @struct(Router)
registers a HEAD route on r and returns the updated router.
| Parameter | Type | Default |
|---|---|---|
r | — | |
pattern | @str | — |
handler | @fn | — |
isRouter(x)
fn slug.web.routes#isRouter(x) -> @bool
returns true if x is a Router struct instance.
| Parameter | Type | Default |
|---|---|---|
x | — |
mount(r, prefix, handler)
fn slug.web.routes#mount(r, @str prefix, @fn handler) -> @struct(Router)
mounts handler at prefix/*, matching any method.
The path remainder after the prefix is available as req.params["*"].
| Parameter | Type | Default |
|---|---|---|
r | — | |
prefix | @str | — |
handler | @fn | — |
mountRouter(r, prefix, childRouter)
fn slug.web.routes#mountRouter(r, @str prefix, childRouter) -> @struct(Router)
mounts a child Router at prefix, handling paths relative to the prefix.
Equivalent to mount(r, prefix, subrouter(childRouter)).
| Parameter | Type | Default |
|---|---|---|
r | — | |
prefix | @str | — |
childRouter | — |
post(r, pattern, handler)
fn slug.web.routes#post(r, @str pattern, @fn handler) -> @struct(Router)
registers a POST route on r and returns the updated router.
| Parameter | Type | Default |
|---|---|---|
r | — | |
pattern | @str | — |
handler | @fn | — |
router()
fn slug.web.routes#router() -> @struct(Router)
creates an empty Router.
static(dir, cacheTimeSeconds)
fn slug.web.routes#static(dir, cacheTimeSeconds = 3600) -> @fn
returns a handler that serves static files from dir.
Intended for use with mount: mount("/static", static("public")). Files are served with the appropriate Content-Type based on extension and cached for cacheTimeSeconds seconds (default 1 hour).
Returns 404 for missing files, path traversal attempts (..), and non-GET/HEAD requests. HEAD requests return headers without a body.
| Parameter | Type | Default |
|---|---|---|
dir | — | |
cacheTimeSeconds | 3600 |
subrouter(router)
fn slug.web.routes#subrouter(router) -> @fn
wraps a Router as a handler function for use with mount.
Strips the mount prefix from the path and clears the "*" param before dispatching to the inner router.
| Parameter | Type | Default |
|---|---|---|
router | — |
withHeader(nil)
fn slug.web.routes#withHeader(nil) -> @fn
wraps handler to add a fixed response header to every response. nil
withLog(handler)
fn slug.web.routes#withLog(handler) -> @fn
wraps handler with request/response logging to stdout.
Logs timestamp, method, elapsed time (ms), status code, path, and any request ID / trace ID / span ID present on the request.
| Parameter | Type | Default |
|---|---|---|
handler | — |
withMaxBody(h, maxBytes)
fn slug.web.routes#withMaxBody(h, maxBytes = 1048576) -> @fn
wraps handler to enforce a maximum request body size.
Checks Content-Length for POST, PUT, and PATCH requests. Returns 413 Payload Too Large if the declared size exceeds maxBytes. Default limit is 1MB (1_048_576 bytes).
| Parameter | Type | Default |
|---|---|---|
h | — | |
maxBytes | 1048576 |
withRecover(h)
fn slug.web.routes#withRecover(h) -> @fn
wraps handler so that any thrown error returns a 500 Server Error response.
Use as the outermost middleware layer to prevent unhandled errors from crashing the connection.
| Parameter | Type | Default |
|---|---|---|
h | — |
withRequestId(handler, newRequestId)
fn slug.web.routes#withRequestId(@fn handler, @fn newRequestId = fn() {randomHexString(32)}) -> @fn
wraps handler to attach a request ID to every request and response.
Uses the incoming X-Request-Id header if present, otherwise generates a new random hex ID. The ID is attached to req.requestId and returned as the X-Request-Id response header.
| Parameter | Type | Default |
|---|---|---|
handler | @fn | — |
newRequestId | @fn | fn() {randomHexString(32)} |
withTimeout(h, ms)
fn slug.web.routes#withTimeout(h, ms) -> @fn
wraps handler with a timeout of ms milliseconds.
If the handler does not return within the timeout, a TimeoutError is thrown.
| Parameter | Type | Default |
|---|---|---|
h | — | |
ms | — |
withTraceContext(handler, newTraceId, newSpanId)
fn slug.web.routes#withTraceContext(handler, @fn newTraceId = fn() {randomHexString(32)}, @fn newSpanId = fn() {randomHexString(16)}) -> @fn
wraps handler to propagate W3C traceparent trace context.
Extracts or generates a traceId and creates a new spanId for each request. Attaches both to req.traceId and req.spanId. Returns the traceparent header on the response for downstream propagation.
Format: 00-<traceId>-<spanId>-01
| Parameter | Type | Default |
|---|---|---|
handler | — | |
newTraceId | @fn | fn() {randomHexString(32)} |
newSpanId | @fn | fn() {randomHexString(16)} |