Skip to content

Route Parameters and Middleware

Prefixing a route segment with : captures its value in req.params. The query string is available in req.query; the ?? operator applies a default when the key is absent:

:id, req.params, req.query and ?? for optional values.

07-server-path-params.zolo
// Feature: HTTP server — path params (`:id`) and query string
// Syntax: `:name` in the route; read via `req.params.name` and `req.query.k`.
// When to use: REST routes with IDs, slugs, optional filters.

use std::http

@get("/users/:id")
fn get_user(req) {
  // `req.params` carries the matched segments.
  let id = req.params.id
  return #{
    id,
    name: "User {id}",
  }
}

@get("/search")
fn search(req) {
  // `req.query` is a map; `??` applies a default if absent.
  let q = req.query.q ?? "(empty)"
  let limit = req.query.limit ?? "10"
  return #{
    q,
    limit,
  }
}

@get("/posts/:author/:slug")
fn get_post(req) {
  return #{
    author: req.params.author,
    slug: req.params.slug,
  }
}

http.serve(3002)
// expected when running:
//   curl http://localhost:3002/users/42                -> {"id":"42","name":"User 42"}
//   curl 'http://localhost:3002/search?q=zolo&limit=5' -> {"q":"zolo","limit":"5"}
//   curl http://localhost:3002/posts/devzolo/hello     -> {"author":"devzolo","slug":"hello"}

Requires the Zolo CLI/host — open in the playground or run locally.

Middleware is a function fn mw(req, next) that can inspect the request, call next(req) to continue the chain and attach headers to the response. Register it with http.middleware(mw) in the router pipe:

Log and CORS middleware — next(req) and .with_headers(#{...}).

08-server-middleware.zolo
// Feature: HTTP server — middleware via pipe
// Syntax: `fn mw(req, next) { ... return next(req) }` and
//         `router |> http.middleware(mw)`
// When to use: logging, CORS, authentication, common headers —
// cross-cutting code shared across routes.

use std::http

// Middleware 1: log every request.
fn logger(req, next) {
  print("[{req.method}] {req.path}")
  return next(req)
}

// Middleware 2: add CORS headers to the response.
fn cors(req, next) {
  return next(req).with_headers(#{
    "Access-Control-Allow-Origin": "*",
    "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE",
    "Access-Control-Allow-Headers": "Content-Type",
  })
}

fn api_health() {
  return #{status: "ok"}
}

fn api_echo(req) {
  return req.json()
}

let router = http.router()
  |> http.get("/api/health", api_health)
  |> http.post("/api/echo", api_echo)
  |> http.middleware(logger)
  |> http.middleware(cors)

http.serve(3003, router)
// expected when running:
//   curl http://localhost:3003/api/health      -> {"status":"ok"}
//   curl -i http://localhost:3003/api/health   -> CORS headers present

Requires the Zolo CLI/host — open in the playground or run locally.

Registration order matters: middlewares are executed in the order they appear in the pipe, before any route handler.

Challenge

Write a simple authentication middleware: if req.headers["Authorization"] is nil, return http.response(401, "Unauthorized") without calling next.

enespt-br