Web Servers (planned)
Luma includes a lightweight, expressive and predictable approach for building web servers.
The design focuses on three principles:
- Minimal boilerplate
- Handlers are just functions
- Clear and explicit types
This document describes the recommended way to build HTTP servers and APIs in Luma using the standard server module.
Getting Started
The simplest possible web server listens on a port and responds with “Hello World”.
fn hello(req: Request) -> Response {
return text("Hello World")
}
fn main() {
routes: [Route] = [
get("/", hello),
]
config: ServerConfig = ServerConfig {
port: 3000,
routes: routes,
middleware: [],
on_error: nil,
}
server.start(config)
}This example demonstrates the fundamental concepts:
- A handler is just a function:
fn(req: Request) -> Response - Routes are collected in a list:
[Route] - The server is started by passing a ServerConfig struct
Routes
Routes are defined using helper functions that associate an HTTP method, a path, and a handler.
routes: [Route] = [
get("/", home),
get("/users", list_users),
post("/users", create_user),
]A route is internally represented as:
struct Route {
method: str
path: str
handler: Handler // alias for (Request) -> Response
}Defining Handlers
Handlers always follow the same form:
fn handler_name(req: Request) -> Response {
// ...
}Example:
fn list_users(req: Request) -> Response {
users: [User] = db.query_users()
return json({ users: users })
}Request Object
Every handler receives a Request value containing:
struct Request {
method: str
path: str
url: str
headers: {str: str}
query: {str: str}
params: {str: str}
body_bytes: [byte]
}Example usage inside a handler:
fn example(req: Request) -> Response {
token: ?str = req.headers.get("Authorization")
search: ?str = req.query.get("search")
id: ?str = req.params.get("id") // for dynamic routes
return text("OK")
}You may extend Request by adding convenience methods inside an impl block:
impl Request {
fn json(self) -> {str: any} {
// parse self.body_bytes
}
}Responses
All HTTP responses are instances of:
struct Response {
status: int
headers: {str: str}
body: [byte]
}A few helper functions are provided:
fn text(body: str) -> Response
fn json(body: {str: any}) -> Response
fn redirect(location: str) -> Response
fn file_download(path: str) -> ResponseExamples:
return text("Hello")
return json({ message: "Created" })
return redirect("/login")To return custom status codes:
fn not_found() -> Response {
return Response {
status: 404,
headers: {},
body: "Not found".to_bytes(),
}
}Middleware
Middleware functions wrap handlers and allow pre- and post-processing of requests.
A middleware has the form:
type Handler = (Request) -> Response
type Middleware = (Request, Handler) -> ResponseExample: logging middleware
fn log_middleware(req: Request, next: Handler) -> Response {
start: int = now().unix_ms()
res: Response = next(req)
duration: int = now().unix_ms() - start
print("${req.method} ${req.path} - ${duration}ms")
return res
}Example: authentication middleware
fn auth_middleware(req: Request, next: Handler) -> Response {
token: ?str = req.headers.get("Authorization")
if token == nil || !valid_token(*token) {
return json({ error: "Unauthorized" })
}
return next(req)
}Middleware is applied through the ServerConfig:
config: ServerConfig = ServerConfig {
port: 3000,
routes: routes,
middleware: [log_middleware, auth_middleware],
on_error: nil,
}Error Handling
Luma allows defining a custom error handler that receives:
- The error value
- The original request
fn my_error_handler(err: Error, req: Request) -> Response {
print("Error: ${err.message}")
return json({ error: "Internal Server Error" })
}Add it to the configuration:
config.on_error = my_error_handlerDynamic Routes
Paths may include parameters:
routes: [Route] = [
get("/users/:id", get_user),
]Inside the handler:
fn get_user(req: Request) -> Response {
id: ?str = req.params.get("id")
if id == nil {
return not_found()
}
user: ?User = db.find_user(*id)
if user == nil {
return not_found()
}
return json({ user: user })
}Complete Example
fn get_users(req: Request) -> Response {
users: [User] = db.query_users()
return json({ users: users })
}
fn create_user(req: Request) -> Response {
data: {str: any} = req.json()
new_id: int = db.insert_user(data)
return json({ id: new_id })
}
fn health(req: Request) -> Response {
return json({ status: "ok", timestamp: now().unix() })
}
routes: [Route] = [
get("/api/users", get_users),
post("/api/users", create_user),
get("/health", health),
]
conf: ServerConfig = ServerConfig {
port: 3000,
routes: routes,
middleware: [],
on_error: nil,
}
server.start(conf)Summary
Luma’s web server design centers on:
- Clear types (
Request,Response,Route,ServerConfig) - Simple handlers (
fn(req) -> Response) - Explicit configuration
- Familiar, composable middleware
- Built-in helpers for JSON, text, redirects, and files
This approach provides a minimal but powerful foundation for building REST APIs, websites, and backend services - while staying fully aligned with Luma’s core principles: clarity, safety, and simplicity.