Skip to content

Middleware and CORS

2 min read

Middleware receives a context and a continuation. Register global layers explicitly on an app.

import system;
import system.webapi;

async fun request_id(ctx: RequestContext, next: Next): HttpOutgoing {
    ctx.set("request_id", ctx.incoming.header("X-Request-ID").unwrap_or("local"));
    let response = next.call().await;
    return response.set_header("X-Request-ID", ctx.get("request_id").unwrap_or(""));
}

@router("")
class Routes {
    @get("/hello")
    public static fun hello(): string {
        return "hello";
    }
}

async fun main(): void {
    let app = WebApp();
    app.include_router(Routes.router()).unwrap();
    app.use(Middleware(request_id)).unwrap();
    app.use(
        Cors.middleware(
            CorsOptions(
                allow_origins: "https://example.com",
                allow_methods: "GET,POST",
                allow_headers: "Authorization,Content-Type"
            )
        )
    ).unwrap();
    app.run("127.0.0.1", 8000).await.unwrap();
}

The first registered layer is outermost. Requests pass through app layers, router @use(handler) layers, route @use(handler) layers, dependency extraction, and the handler. Responses return in reverse order. Repeated @use attributes preserve source order.

A layer may return early, for example to reject a request or answer a preflight, without calling next. Call next.call().await once to continue. App middleware also surrounds documentation and unmatched-route responses.

Middleware building blocks

Name What it is
Middleware(handler) Wraps an async fun(ctx: RequestContext, next: Next): HttpOutgoing so app.use can install it.
next.call() Runs the rest of the pipeline and returns its Future<HttpOutgoing>.
@use(handler) Applies a middleware function to one router class or one route. Repeatable.
app.use(middleware) Applies middleware to every request the app handles, including documentation pages and 404s.
Cors.middleware(options) Built-in middleware for cross-origin requests.

CORS options

List values are comma-separated strings, such as "GET,POST".

CorsOptions field Default Effect
allow_origins "*" Origins allowed to call the API, or * for any.
allow_methods "*" Methods allowed in preflight responses. * lists all standard methods.
allow_headers "*" Request headers allowed in preflight responses. * echoes the headers the browser asks for.
expose_headers "" Response headers that browser code may read.
allow_credentials false Allow cookies and Authorization on cross-origin requests.
max_age 600 Seconds a browser may cache a preflight response.

With credentials enabled, the response echoes the allowed request origin instead of sending *, because browsers reject credentials with a wildcard origin. Include custom response headers in expose_headers when browser code must read them.

See the API lookup and complete example.