HTTP Server

Zolo includes a built-in HTTP server module for building web applications and APIs. Import it with use std::http.

Quick Start #

use std::http

fn handle_root(_req) {
  return "Hello from Zolo!"
}

let app = http.router()
  |> http.get("/", handle_root)

http.serve(3000, app)

This starts an HTTP server on port 3000 that responds with "Hello from Zolo!" on the root path.

Router and Routes #

Create a router with http.router() and register route handlers using http.get() and http.post(). Routes are chained with the pipe operator |>:

let app = http.router()
  |> http.get("/health", health_handler)
  |> http.get("/users/:id", get_user)
  |> http.post("/echo", echo_handler)
Function Description
http.router() Create a new router
http.get(router, path, handler) Register a GET route
http.post(router, path, handler) Register a POST route

Path Parameters #

Define dynamic segments in routes with :param syntax. Access them via req.params:

fn handle_user(req) {
  let id = req.params.id
  return #{"id": id, "name": "User"}
}

fn handle_greet(req) {
  let name = req.params.name
  return #{"greeting": "Hello, {name}!"}
}

let app = http.router()
  |> http.get("/users/:id", handle_user)
  |> http.get("/api/greet/:name", handle_greet)

A request to /users/42 sets req.params.id to "42".

Query Parameters #

Access query string parameters via req.query. Use ?? to provide defaults:

fn handle_greet(req) {
  let name = req.query.name ?? "World"
  return "Hello, {name}!"
}

A request to /greet?name=Alice sets req.query.name to "Alice".

Request Object #

The handler receives a request object with the following fields:

Field Type Description
req.method str HTTP method ("GET", "POST", etc.)
req.path str Request path (e.g. "/users/42")
req.params table Path parameters (e.g. req.params.id)
req.query table Query string parameters (e.g. req.query.name)
req.headers table Request headers
req.body str Raw request body
req.json() fn Parse body as JSON and return a table

Reading JSON Body #

Use req.json() to parse incoming JSON payloads:

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

With the pipe operator:

fn handle_echo(req) {
  return req |> .json()
}

Response Types #

Handlers return a value that determines the response format:

Return Value Content-Type Behavior
String text/plain Returned as plain text with status 200
Table (#{}) application/json Automatically serialized to JSON with status 200
http.html(body) text/html Returned as HTML with status 200
http.redirect(url) Sends a redirect response to the given URL
http.response(status, body) text/plain Returned with a custom status code

Examples #

// Plain text
fn text_handler(_req) {
  return "Hello from Zolo!"
}

// JSON (return a table)
fn json_handler(_req) {
  return #{"message": "Hello", "language": "Zolo", "version": "0.1.0"}
}

// HTML
fn html_handler(_req) {
  return http.html("<h1>Hello from Zolo!</h1>")
}

// Redirect
fn redirect_handler(_req) {
  return http.redirect("/")
}

// Custom status code
fn created_handler(_req) {
  return http.response(201, "Created!")
}

Adding Headers to a Response #

Use .with_headers() to attach custom headers to a response:

fn handler(req) {
  return #{"data": "value"}.with_headers(#{
    "X-Custom-Header": "custom-value"
  })
}

Middleware #

Middleware functions wrap route handlers to add cross-cutting behavior such as logging, authentication, or CORS headers. A middleware receives the request and a next function:

fn logger(req, next) {
  print("[{req.method}] {req.path}")
  return next(req)
}

Register middleware with http.middleware():

let app = http.router()
  |> http.get("/api/health", health_handler)
  |> http.middleware(logger)

Middleware can also modify the response by chaining .with_headers() on the result of next:

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"
  })
}

Multiple middleware functions can be stacked:

let app = http.router()
  |> http.get("/api/health", health_handler)
  |> http.middleware(logger)
  |> http.middleware(cors)

Workers Configuration #

Use http.workers(n) to set the number of worker threads for the server:

http.workers(1)
http.serve(3000, app)

Complete Example #

use std::http

// --- Handlers ---

fn handle_root(_req) {
  return "Hello from Zolo!"
}

fn handle_json(_req) {
  return #{"message": "Hello", "language": "Zolo", "version": "0.1.0"}
}

fn handle_user(req) {
  let id = req.params.id
  return #{"id": id, "name": "User"}
}

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

fn handle_greet(req) {
  let name = req.query.name ?? "World"
  return "Hello, {name}!"
}

fn handle_html(_req) {
  return http.html("<h1>Hello from Zolo!</h1>")
}

fn handle_redirect(_req) {
  return http.redirect("/")
}

fn handle_status(_req) {
  return http.response(201, "Created!")
}

// --- Middleware ---

fn logger(req, next) {
  print("[{req.method}] {req.path}")
  return next(req)
}

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"
  })
}

// --- Router ---

let app = http.router()
  |> http.get("/", handle_root)
  |> http.get("/json", handle_json)
  |> http.get("/users/:id", handle_user)
  |> http.post("/echo", handle_echo)
  |> http.get("/greet", handle_greet)
  |> http.get("/html", handle_html)
  |> http.get("/redirect", handle_redirect)
  |> http.get("/status", handle_status)
  |> http.middleware(logger)
  |> http.middleware(cors)

// --- Start server ---

http.workers(1)
http.serve(3000, app)

API Reference #

Function Description
http.router() Create a new router
http.get(router, path, handler) Register a GET route
http.post(router, path, handler) Register a POST route
http.middleware(router, fn) Add middleware to the router
http.serve(port, router) Start the server on the given port
http.workers(n) Set the number of worker threads
http.html(body) Create an HTML response
http.redirect(url) Create a redirect response
http.response(status, body) Create a response with a custom status code
enespt-br