moonback

    A web backend framework for MoonBit

    moonbit
    web
    http
    server
    async
    Download zip
    Version
    0.8.3
    License
    Apache-2.0
    Last updated
    6 days ago
    Downloads
    164

    Dependencies

    #MoonBack

    MoonBack is an async web backend framework for MoonBit.

    It provides a small routing layer, composable application modules, middleware, typed request helpers, response helpers, graceful shutdown hooks, and module-scoped dependency injection.

    #Features

    • Async HTTP server integration through moonbitlang/async
    • Module-based app composition with Module and ModuleContext
    • Route registration for GET, POST, and arbitrary HTTP methods
    • Middleware chaining
    • Typed query parameter and cookie helpers
    • Response helpers for text, type-safe HTML, JSON, empty responses, and WebSocket upgrade
    • App lifecycle cleanup with ctx.on_close
    • Module-only dependency injection using TypedKey
    • Typed custom application configuration values
    • Optional app mounting for composing independent apps

    #Installation

    Add MoonBack to your moon.mod:

    import {
    "moonbitlang/async@0.21.0",
    "moonbitlang/moonback@0.8.2",
    }

    MoonBack is intended for the native target:

    preferred_target = "native"

    #Migrating from hackwaly/moonback

    Starting with version 0.8.2, MoonBack is published as moonbitlang/moonback. Replace the old dependency in moon.mod:

    import {
    "moonbitlang/moonback@0.8.2",
    }

    Application source code can continue to use the @moonback package alias. If you import an experimental middleware directly, update its full package path from hackwaly/moonback/middlewares/... to moonbitlang/moonback/middlewares/... as well.

    #Quick start

    async fn main {
    let app = @moonback.App(ctx => {
    ctx.get("/", (_req, res) => {
    res.send_text("Hello, MoonBack!")
    })
    })
    defer app.close()

    let server = @moonback.listen(port=3000)
    println("Server listening on \{server.addr}")
    app.serve(server)
    }

    Run it with:

    moon run .

    Then open:

    http://127.0.0.1:3000/

    #Modules

    A Module is a reusable unit of application initialization. Modules register routes, middleware, mounted apps, close hooks, and dependencies.

    let posts_module : @moonback.Module = @moonback.Module(ctx => {
    ctx.get("/posts", list_posts)
    ctx.get("/posts/:id", show_post)
    })

    let admin_module : @moonback.Module = @moonback.Module(ctx => {
    ctx.add_middleware(require_admin)
    ctx.get("/admin/users", list_users)
    })

    async fn main {
    let app = @moonback.App(ctx => {
    ctx.use_(posts_module)
    ctx.use_(admin_module)
    })
    defer app.close()

    let server = @moonback.listen(port=3000)
    app.serve(server)
    }

    ctx.use_ initializes the child module immediately in the same app context.

    #Routing

    MoonBack supports static paths, named parameters, and catch-all routes.

    ctx.get("/", (_req, res) => {
    res.send_text("home")
    })

    ctx.get("/posts/:id", (req, res) => {
    let id = req.params["id"]
    res.send_text("post \{id}")
    })

    ctx.add_route(@http.Put, "/posts/:id", update_post)

    When using add_route from your own package, import moonbitlang/async/http and refer to methods as @http.Get, @http.Put, and so on.

    Supported helpers:

    ctx.get(path, handler) ctx.post(path, handler) ctx.add_route(method, path, handler)

    #Requests

    Handlers receive a Request and a Responder.

    ctx.get("/search", (req, res) => {
    let q = match req.query().get("q") {
    Some(q) => q
    None => ""
    }
    let cookies = req.cookie()
    let has_session = match cookies.get("session") {
    Some(_) => true
    None => false
    }
    let ip = match req.ip() {
    Some(ip) => ip.to_string()
    None => ""
    }

    res.send_json({
    "query": q,
    "has_session": has_session,
    "ip": ip,
    })
    })

    Useful request APIs:

    • req.meth
    • req.path
    • req.search
    • req.headers
    • req.params
    • req.body.text()
    • req.body.binary()
    • req.body.json()
    • req.query()
    • req.cookie()
    • req.ip()
    • req.ips()

    MoonBack cancels the in-flight handler when it observes that the client connection has been closed. This lets long-running handlers stop promptly and release server-side resources instead of continuing work for a client that can no longer receive the response.

    Disconnects are observed through request or response I/O, such as an I/O error while reading the request body or writing the response. If a handler does not read the request body and does not write a response, there may be no I/O operation that can discover the disconnect. Long-running or expensive handlers should consume req.body when they need MoonBack to notice clients that close the connection before the declared body has been fully sent.

    #Responses

    Use Responder helpers for common response types:

    res.send_text("ok")
    res.send_html(@moonback.Html::raw("<h1>Hello</h1>"))
    res.send_json({ "ok": true })
    res.send_void(status=204)

    send_html accepts Html instead of a plain String, so escaped content and trusted raw markup are explicit:

    let page = @moonback.Html(builder => {
    let title = "Hello, <MoonBack>"
    builder "<h1>\{title}</h1>"
    })

    res.send_html(page)

    Use Html::escape for dynamic text and Html(raw=...) only for markup you already trust.

    For streaming responses, use respond:

    ctx.get("/stream", (_req, res) => {
    let w = res.respond(status=200, headers={ "Content-Type": "text/plain" })
    w.write("first line\n")
    w.flush()
    w.write("second line\n")
    })

    #WebSocket

    Use Responder::upgrade to upgrade an HTTP request to a WebSocket connection. After the upgrade succeeds, do not send an HTTP response from the handler; use the returned @websocket.Conn for bidirectional communication.

    Add the WebSocket package to your moon.pkg when you need to handle messages:

    import {
    "moonbitlang/async/websocket",
    "moonbitlang/moonback",
    }

    Register a WebSocket route like any other route:

    async fn serve_websocket(
    req : @moonback.Request,
    res : @moonback.Responder,
    ) -> Unit {
    let ws = res.upgrade(req)
    defer ws.close()

    try {
    for ;; {
    let msg = ws.recv()
    match msg.kind {
    Text => {
    let text = msg.read_all().text()
    ws.send_text("echo: \{text}")
    }
    Binary => {
    let data = msg.read_all().binary()
    ws.send_binary(data)
    }
    }
    }
    } catch {
    @websocket.ConnectionClosed(_, _) => ()
    err => raise err
    }
    }

    let app = @moonback.App(ctx => {
    ctx.get("/ws", serve_websocket)
    })

    The returned WebSocket connection is valid only during request handling. Do not store it after the handler finishes.

    #Middleware

    A middleware wraps the next handler.

    let log_requests = @moonback.Middleware(next => {
    (req, res) => {
    println("\{req.meth} \{req.path}")
    next(req, res)
    }
    })

    let app = @moonback.App(ctx => {
    ctx.add_middleware(log_requests)
    ctx.get("/", (_req, res) => res.send_text("ok"))
    })

    Middleware is applied in registration order.

    #Dependency injection

    MoonBack dependency injection is intentionally available only during module initialization through ModuleContext. Handlers should capture dependencies from module initialization instead of resolving them from App or Request.

    Define a typed key:

    struct Greeter {
    prefix : String
    }

    @moonback.TypedBox {
    GreeterDependency(Greeter)
    }

    let greeter_key : @moonback.TypedKey[Greeter] = @moonback.TypedKey(
    name="greeter",
    box=greeter => GreeterDependency(greeter),
    unbox=err => {
    match err {
    GreeterDependency(greeter) => Some(greeter)
    _ => None
    }
    },
    )

    Provide and require it from modules:

    let greeter_module : @moonback.Module = @moonback.Module(ctx => {
    let greeter = ctx.require(greeter_key)

    ctx.get("/", (_req, res) => {
    res.send_text("\{greeter.prefix}, MoonBack!")
    })
    })

    let app = @moonback.App(ctx => {
    ctx.provide(greeter_key, { prefix: "Hello" })
    ctx.use_(greeter_module)
    })

    Available APIs:

    ctx.provide(key, value)
    ctx.resolve(key)
    ctx.require(key)

    provide fails on duplicate dependencies. require fails when a dependency is missing. resolve returns None when a dependency is missing.

    See ../examples/dependency_injection for a complete runnable example.

    #Request userdata

    TypedKey can also be used for request-scoped userdata, which is useful for middleware that computes values for later handlers.

    req.ctx.set_userdata(key, value)
    req.ctx.get_userdata(key)

    Dependency injection and request userdata use the same key type, but different stores and lifetimes:

    • dependencies are app-scoped and module-initialization only
    • userdata is request-scoped

    #App configuration

    let app = @moonback.App(
    root_module(),
    config=@moonback.Config(
    trust_proxy=true,
    max_connections=1024,
    stop_timeout=5.0,
    ),
    )

    Config fields:

    • trust_proxy: use proxy headers such as X-Forwarded-For
    • max_connections: limit concurrent accepted connections
    • stop_timeout: graceful shutdown timeout in seconds

    You can also attach typed custom values to app configuration. Custom config uses the same TypedKey pattern as dependency injection and request userdata, but values are stored on Config and are available through app.config().

    @moonback.TypedBox {
    UploadRoot(String)
    }

    let upload_root_key : @moonback.TypedKey[String] = @moonback.TypedKey(
    name="upload_root",
    box=root => UploadRoot(root),
    unbox=boxed => {
    match boxed {
    UploadRoot(root) => Some(root)
    _ => None
    }
    },
    )

    let app = @moonback.App(
    root_module(),
    config=@moonback.Config(
    custom=[
    @moonback.Config::custom(upload_root_key, "/srv/uploads"),
    ],
    ),
    )

    let upload_root = app.config().get_custom(upload_root_key)

    #Cleanup hooks

    Register cleanup callbacks with ctx.on_close:

    let app = @moonback.App(ctx => {
    let db = connect_db()
    ctx.on_close(() => db.close())
    })

    defer app.close()

    Close hooks run in reverse registration order.

    #Mounting apps

    Mount another app under a path:

    let api = @moonback.App(ctx => {
    ctx.get("/health", (_req, res) => res.send_text("ok"))
    })

    let app = @moonback.App(ctx => {
    ctx.mount("/api", api)
    })

    Mounted apps keep their own routes and middleware. By default, their unhandled errors propagate through the parent app's middleware and final error boundary, so application-wide error handling can be registered once on the parent.

    Use error_mode=Isolate when a mounted app should handle its own unhandled errors instead:

    ctx.mount("/api", api, error_mode=@moonback.Isolate)

    By default, the parent app owns and closes the mounted app. Set take_ownership=false when its lifecycle is managed elsewhere.

    #Development

    Useful commands:

    moon update moon check moon test moon fmt moon info

    moon info updates pkg.generated.mbti, which records the public API surface.

    #License

    Licensed under the Apache License 2.0.

    #Embedded static assets

    moonbitlang/moonback/middlewares/unstable_static can serve files embedded in a native executable, such as the frontend assets generated by warren build--bundle, without a separate static directory:

    ctx.add_middleware(
    @static.from_assets(
    warren_assets(), // Map[String, Bytes], keyed by paths such as /index.js
    rewrite_trailing_slash_index=true,
    ),
    )

    from_assets copies the asset map and supports GET/HEAD, MIME types, directory indexes, and history_fallback like new(root=...). Missing resources fall through to application routes. Asset keys are decoded absolute URL paths; request paths are percent-decoded once for both memory and filesystem sources.

    Handler

    type Handler = async (Request, Responder) -> Unit

    The async function signature for request handlers.

    Extension

    pub(open) trait Extension {
    async fn initialize(self : Self, ctx : ModuleContext) -> Unit
    }

    Implemented by values that can extend a module context during initialization.

    Module implements this trait, so ctx.use_(some_module) works directly. Custom extension values can also implement Extension when you want to package reusable setup logic without exposing it as a raw module.

    Flusher

    pub(open) trait Flusher {
    async fn flush(self : Self) -> Unit
    }

    Flushes buffered output to the underlying transport.

    Listener

    pub(open) trait Listener {
    async fn accept(self : Self) -> (
    ServerConnection
    ,
    Addr
    )
    fn close(self : Self) -> Unit
    }

    A trait for accepting incoming network connections.

    Implementations of this trait are responsible for listening on a network socket and accepting new client connections.

    Methods

    • accept - Asynchronously waits for and accepts the next incoming connection. Returns a tuple containing the established HTTP server connection and the client's socket address.

    • close - Closes the listener and stops accepting new connections.

    ToHeaders

    pub trait ToHeaders {
    fn to_headers(Self) -> Headers
    }

    WriteFlusher

    pub(open) trait WriteFlusher :
    Writer
    + Flusher {
    }

    A writer that also supports flushing buffered output.

    WriteToHtmlBuilder

    pub trait WriteToHtmlBuilder {
    async fn write_to(Self, HtmlBuilder) -> Unit
    }

    A value that knows how to write itself into an HtmlBuilder.

    Implement this trait for types that should be usable in HTML template interpolation, for example b <+ "<p>\{value}</p>".

    DependencyError

    pub suberror DependencyError {
    MissingDependency(name~ : String)
    DuplicateDependency(name~ : String)
    }

    RouterError

    pub suberror RouterError {
    RouteConflict(path~ : StringView)
    }

    App

    pub struct App {
    // private fields
    }

    A configured application that can handle requests and serve listeners.

    App::App

    async fn App::App(root : Module, config? : Config) -> App

    Creates an app from the root module and configuration.

    The root module is the composition entry of the application. It can register routes directly, or assemble the app from smaller reusable modules through ModuleContext::use_.

    This returns after the root module has finished loading.

    App::close

    fn App::close(self : App) -> Unit

    Unload root module - run registered on_close hooks. The app should not be used after this.

    Example:
    let app = @moonback.App((ctx) => { ... })
    defer app.close()
    app.serve(@moonback.listen())

    App::config

    fn App::config(self : App) -> Config

    Returns the application's configuration.

    App::handle_request

    async fn App::handle_request(self : App, req : Request, res : Responder) -> Unit

    Handles a prepared request with this app.

    App::serve

    async fn App::serve(self : App, listener : &Listener, take_ownership? : Bool) -> Unit

    Starts accepting connections from listener and serves requests on them. This returns when serving stops or when the surrounding task is cancelled.

    If take_ownership is true (true by default), listener.close() is called / before returning.

    On cancellation, in-flight connections are given up to Config.stop_timeout seconds to finish gracefully.

    Config

    pub struct Config {
    max_connections : Int?
    stop_timeout : Double
    trust_proxy : Bool
    // private fields
    }

    Runtime configuration for an application instance.
    impl Default for Config

    Config::Config

    fn Config::Config(max_connections? : Int, stop_timeout? : Double, trust_proxy? : Bool, custom? : ArrayView[CustomConfigEntry]) -> Config

    Creates a configuration value for a new application. stop_timeout defaults to 30 seconds. trust_proxy defaults to false.

    Config::custom

    fn[T] Config::custom(key : TypedKey[T], value : T) -> CustomConfigEntry

    Config::default

    fn Config::default() -> Config

    Config::get_custom

    fn[T] Config::get_custom(self : Config, key : TypedKey[T]) -> T?

    Context

    type Context

    Context::get_userdata

    fn[T] Context::get_userdata(self : Context, key : TypedKey[T]) -> T?

    Returns a typed userdata value previously stored under key.

    Context::set_userdata

    fn[T] Context::set_userdata(self : Context, key : TypedKey[T], val : T) -> Unit

    Stores a typed userdata value under key.

    Context::with_middlewares

    fn Context::with_middlewares(_self : Context, middlewares : ArrayView[Middleware], handler : async (Request, Responder) -> Unit) -> (async (Request, Responder) -> Unit)

    Wraps a handler with middlewares. The middlewares will be applied in the order they are given. Example:
    ctx.get(
    "/",
    ctx.with_middlewares([middleware1, middleware2]) ((req, res) => {
    ...
    })
    )

    CustomConfigEntry

    type CustomConfigEntry

    Headers

    type Headers

    impl Default for Headers

    Headers::Headers

    fn Headers::Headers() -> Headers

    Headers::default

    fn Headers::default() -> Headers

    Headers::from

    fn Headers::from(from : &ToHeaders) -> Headers

    Converts any ToHeaders value into Headers.

    Headers::get

    fn Headers::get(self : Headers, name : String) -> StringView?

    Returns a header value by name. Multiple values are joined with , .

    Headers::get_all

    fn Headers::get_all(self : Headers, name : String) -> ReadOnlyArray[StringView]

    Returns all values for a header name. For regular headers, comma-separated values are split into a list.

    Headers::get_list

    fn Headers::get_list(self : Headers, name : String) -> ReadOnlyArray[StringView]

    Returns all values for a header name. For regular headers, comma-separated values are split into a list.

    Headers::iter

    fn Headers::iter(self : Headers) -> Iter[(String, String)]

    Iterates over all headers as lowercase name-value pairs.

    Headers::to_headers

    fn Headers::to_headers(self : Headers) -> Headers

    Headers::to_mut

    fn Headers::to_mut(self : Headers) -> MutHeaders

    Html

    type Html

    An HTML fragment that can stream UTF-8 bytes asynchronously.

    Html separates trusted markup from dynamic text. Raw fragments are written unchanged, while String values written through template interpolation are HTML-escaped.

    Example

    test {
    let title = "Hello, <MoonBack>"
    let badge = @moonback.Html::raw("<span class=\"badge\">new</span>")
    let _card = @moonback.Html(b => {
    b "<article class=\"card\">"
    b "<h1>\{title}</h1>"
    b "\{badge}"
    b "</article>"
    })
    }

    Html::Html

    fn Html::Html(f : async (HtmlBuilder) -> Unit) -> Html

    Builds an HTML fragment with an HtmlBuilder.

    This is the main entry point for template writing. Static template text is treated as trusted markup. Interpolated String values are escaped, while interpolated Html fragments are written as-is.

    Example

    test {
    let title = "Hello, <MoonBack>"
    let badge = @moonback.Html::raw("<span class=\"badge\">new</span>")
    let _card = @moonback.Html(b => {
    b "<article class=\"card\">"
    b "<h1>\{title}</h1>"
    b "\{badge}"
    b "</article>"
    })
    }

    Html::escape

    fn Html::escape(str : String) -> Html

    Creates an HTML fragment by escaping a plain text string.

    Example

    test {
    let _html = @moonback.Html::escape("Tom & <Jerry>")
    }

    Html::raw

    fn Html::raw(raw : String) -> Html

    Creates an HTML fragment from trusted raw markup.

    Use this only for markup you already trust. For dynamic text, use Html::escape or template interpolation with a String.

    Example

    test {
    let _html = @moonback.Html::raw("<strong>safe</strong>")
    }

    Html::raw_utf8_chunk

    fn Html::raw_utf8_chunk(chunk : Bytes) -> Html

    Creates an HTML fragment from trusted UTF-8 bytes.

    The bytes are written unchanged. The caller is responsible for ensuring that they contain valid UTF-8 and trusted markup.

    Html::write_to

    async fn Html::write_to(self : Html, builder : HtmlBuilder) -> Unit

    HtmlBuilder

    type HtmlBuilder

    A streaming builder used by Html.

    Literal parts of a template are written as trusted markup. Interpolated values go through WriteToHtmlBuilder; the built-in String implementation escapes HTML-sensitive characters.

    HtmlBuilder::write_string

    async fn HtmlBuilder::write_string(self : HtmlBuilder, str : String) -> Unit

    Writes trusted text to the output without escaping.

    Prefer template interpolation for dynamic values. write_string is the low-level primitive used for already-escaped strings and trusted literal markup.

    HtmlBuilder::write_string_interpolation

    async fn[T : WriteToHtmlBuilder] HtmlBuilder::write_string_interpolation(self : HtmlBuilder, value : T) -> Unit

    Writes an interpolated template value.

    This method is used by MoonBit template writing. String values are escaped. Html values are written unchanged.

    Example

    test {
    let _page = @moonback.Html(b => {
    let str = "<MoonBack>"
    let br = @moonback.Html::raw("<br>")
    b "\{str}"
    b "\{br}"
    })
    }

    Middleware

    pub(all) struct Middleware((async (Request, Responder) -> Unit) -> (async (Request, Responder) -> Unit))

    A middleware is a newtype of a function that takes a next handler, and returns a new handler. Example:
    let auth_middleware = Middleware(next => {
    (req, res) => {
    if is_authenticated(req) {
    next(req, res)
    } else {
    res.send_void(status=401)
    }
    }
    })

    Middleware::chain

    fn Middleware::chain(middlewares : ArrayView[Middleware]) -> Middleware

    Chains middlewares in declaration order.

    Middleware::compose

    fn Middleware::compose(self : Middleware, other : Middleware) -> Middleware

    Composes two middlewares into one. The current middleware runs before other.

    Middleware::pass_through

    fn Middleware::pass_through() -> Middleware

    Returns a middleware that forwards requests without modification.

    Module

    pub(all) struct Module(async (ModuleContext) -> Unit)

    A Module is a reusable unit of app initialization.

    A module runs once when an App is created. Inside the module you use ModuleContext to describe one slice of the application, for example:

    • register routes
    • append middlewares
    • mount sub-apps
    • register cleanup hooks with on_close

    This is the main mechanism for splitting business logic into small, composable pieces. A feature such as auth, admin, blog api, or payments can each be modeled as a module and then assembled into a root module.

    Because modules are plain values, they are easy to:

    • compose with other modules
    • reuse across multiple apps
    • encapsulate feature-specific setup
    • test in isolation by creating an app from just that module

    Module focuses on describing application structure, not starting the server. Server startup still happens through App::serve.

    Example:
    let posts_module = Module(ctx => {
    ctx.get("/posts", list_posts)
    ctx.get("/posts/:id", show_post)
    })

    let admin_module = Module(ctx => {
    ctx.add_middleware(require_admin)
    ctx.get("/users", list_users)
    })

    let root = Module(ctx => {
    ctx.use_(posts_module)
    ctx.use_(admin_module)
    })

    let app = @moonback.App(root)
    impl Extension for Module

    Module::initialize

    async fn Module::initialize(self : Module, ctx : ModuleContext) -> Unit

    ModuleContext

    type ModuleContext

    ModuleContext::add_middleware

    fn ModuleContext::add_middleware(self : ModuleContext, middleware : Middleware) -> Unit

    Appends a middleware to the current app.

    ModuleContext::add_route

    fn ModuleContext::add_route(self : ModuleContext, meth :
    RequestMethod
    , path : String, handler : async (Request, Responder) -> Unit) -> Unit raise RouterError

    Registers a route for the given HTTP method and path.

    ModuleContext::config

    fn ModuleContext::config(self : ModuleContext) -> Config

    Returns the configuration for the app being initialized.

    ModuleContext::get

    fn ModuleContext::get(self : ModuleContext, path : String, handler : async (Request, Responder) -> Unit) -> Unit raise RouterError

    Registers a GET route.

    ModuleContext::mount

    fn ModuleContext::mount(self : ModuleContext, path : String, app : App, take_ownership? : Bool, error_mode? : MountErrorMode) -> Unit raise RouterError

    Mounts another app under path.

    By default, unhandled errors propagate through the parent app's middleware and final error boundary. Set error_mode to Isolate to make the mounted app handle its own unhandled errors instead.

    When take_ownership is true (true by default), the mounted app is closed with the parent.

    ModuleContext::on_close

    fn ModuleContext::on_close(self : ModuleContext, f : () -> Unit) -> Unit

    Registers a callback that runs when the owning app is closed.

    ModuleContext::post

    fn ModuleContext::post(self : ModuleContext, path : String, handler : async (Request, Responder) -> Unit) -> Unit raise RouterError

    Registers a POST route.

    ModuleContext::provide

    fn[T] ModuleContext::provide(self : ModuleContext, key : TypedKey[T], value : T) -> Unit raise DependencyError

    Provides an app-scoped dependency during module initialization.

    ModuleContext::require

    fn[T] ModuleContext::require(self : ModuleContext, key : TypedKey[T]) -> T raise DependencyError

    Requires an app-scoped dependency during module initialization.

    ModuleContext::resolve

    fn[T] ModuleContext::resolve(self : ModuleContext, key : TypedKey[T]) -> T?

    Resolves an app-scoped dependency during module initialization.

    ModuleContext::use_

    async fn ModuleContext::use_(self : ModuleContext, ext : &Extension) -> Unit

    Applies an extension to the current module context.

    This is the composition point for modules. The extension is initialized immediately and can keep registering routes, middlewares, mounts, and close hooks on the current app.

    The most common use is composing feature modules:

    let api = Module(ctx => ctx.get("/health", health_handler))

    let root = Module(ctx => ctx.use_(api))

    MountErrorMode

    pub(all) enum MountErrorMode {
    Propagate
    Isolate
    }

    Controls whether an unhandled error from a mounted app propagates to its parent app or is handled by the mounted app's own final error boundary.

    MutHeaders

    type MutHeaders

    MutHeaders::MutHeaders

    fn MutHeaders::MutHeaders() -> MutHeaders

    MutHeaders::add

    fn MutHeaders::add(self : MutHeaders, name : String, value : String) -> Unit

    Adds a header value to the existing values for name.
    fn MutHeaders::add_set_cookie(self : MutHeaders, cookie :
    Cookie
    ) -> Unit

    Adds a Set-Cookie header value.

    MutHeaders::from

    fn MutHeaders::from(headers : &ToHeaders) -> MutHeaders

    MutHeaders::get

    fn MutHeaders::get(self : MutHeaders, name : String) -> StringView?

    Returns a header value by name. Multiple values are joined with , .

    MutHeaders::get_all

    fn MutHeaders::get_all(self : MutHeaders, name : String) -> ReadOnlyArray[StringView]

    Returns all values for a header name. For regular headers, comma-separated values are split into a list.

    MutHeaders::iter

    fn MutHeaders::iter(self : MutHeaders) -> Iter[(String, String)]

    Iterates over all headers as lowercase name-value pairs.

    MutHeaders::remove

    fn MutHeaders::remove(self : MutHeaders, name : String) -> Unit

    Removes all values for the given header name.

    MutHeaders::set

    fn MutHeaders::set(self : MutHeaders, name : String, value : String) -> Unit

    Sets a header value, replacing any existing values.

    MutHeaders::to_headers

    fn MutHeaders::to_headers(self : MutHeaders) -> Headers

    MutHeaders::to_immut

    fn MutHeaders::to_immut(self : MutHeaders) -> Headers

    QueryParams

    pub struct QueryParams {
    // private fields
    }

    Decoded query parameters from a request URL.

    Query decoding follows application/x-www-form-urlencoded: + decodes to a space, %20 also decodes to a space, and a literal plus sign must be encoded as %2B.

    QueryParams::get

    fn QueryParams::get(self : QueryParams, name : StringView) -> String?

    Returns the first value for a query parameter name, if present.

    QueryParams::get_all

    fn QueryParams::get_all(self : QueryParams, name : StringView) -> ReadOnlyArray[String]

    Returns all values for a query parameter name in request order.

    QueryParams::has

    fn QueryParams::has(self : QueryParams, name : StringView) -> Bool

    Returns whether a query parameter name is present.

    QueryParams::iter

    fn QueryParams::iter(self : QueryParams) -> Iter[(String, String)]

    Iterates over decoded query parameter pairs in request order.

    Request

    pub(all) struct Request {
    ctx : Context
    meth :
    RequestMethod

    path : StringView
    search : StringView?
    headers : Headers
    params : Map[StringView, StringView]
    body : RequestBody
    }

    Represents an incoming HTTP request handled by the application.

    Request::app

    fn Request::app(self : Request) -> App

    Returns the app instance handling this request. This can be used to access app-level configuration and state.

    Request::cookie

    fn Request::cookie(self : Request) -> Map[String, String]

    Returns the cookies sent with this request as a map of cookie name to value. Cookies are parsed on demand and cached in the request context.

    Request::ip

    fn Request::ip(self : Request) -> StringView?

    Returns the first client IP address for this request, if one is available.

    Request::ips

    fn Request::ips(self : Request) -> ArrayView[StringView]

    Returns the client IP chain for this request. When trust_proxy is enabled, this prefers X-Forwarded-For.

    Request::query

    fn Request::query(self : Request) -> QueryParams

    Returns the decoded query parameters for this request.

    The raw query string is available as request.search. Query parameters are parsed on demand and cached in the request context. Repeated names are preserved; get returns the first value and get_all returns all values. Decoding follows application/x-www-form-urlencoded: + and %20 decode to a space; encode a literal plus sign as %2B.

    RequestBody

    type RequestBody

    Represents the body of an HTTP request, which can be read as a stream or buffered in memory.

    RequestBody::binary

    async fn RequestBody::binary(self : RequestBody) -> Bytes

    Reads the full request body as raw bytes.

    This buffers the entire body in memory, so it should only be used for small request bodies or when the content length is known to be reasonable.

    RequestBody::json

    async fn RequestBody::json(self : RequestBody) -> Json

    Reads the full request body and parses it as JSON.

    This buffers the entire body in memory, so it should only be used for small request bodies or when the content length is known to be reasonable.

    RequestBody::reader

    fn RequestBody::reader(self : RequestBody, no_buffer? : Bool) -> &
    Reader
    raise

    Returns the request body as a stream reader. This can only be called once, and no_buffer=false is not supported yet.

    RequestBody::text

    async fn RequestBody::text(self : RequestBody) -> String

    Reads the full request body as text.

    This buffers the entire body in memory, so it should only be used for small request bodies or when the content length is known to be reasonable.

    Responder

    pub(all) struct Responder(async (Int, Headers) -> &WriteFlusher)

    A responder is newtype of an asynchronous function that takes a status code and headers, sends the response status line and headers, and returns a writer for the response body.

    Important Notes: The responder is only valid during request handling. Do not store it or use it after the response cycle completes.

    Responder::respond

    async fn Responder::respond(self : Responder, status? : Int, headers? : &ToHeaders) -> &WriteFlusher

    Sends the status line and headers, then returns the body writer.

    Responder::send_html

    async fn Responder::send_html(self : Responder, html : Html, status? : Int, headers? : &ToHeaders) -> Unit

    Sends an HTML response with UTF-8 content type.

    Responder::send_json

    async fn Responder::send_json(self : Responder, json : Json, status? : Int, headers? : &ToHeaders, indent? : Int) -> Unit

    Sends a JSON response with the appropriate content type.

    Responder::send_text

    async fn Responder::send_text(self : Responder, text : String, status? : Int, headers? : &ToHeaders) -> Unit

    Sends a plain text response with UTF-8 content type.

    Responder::send_void

    async fn Responder::send_void(self : Responder, status? : Int, headers? : &ToHeaders) -> Unit

    Sends a response without writing a body.

    Responder::upgrade

    Upgrades the current HTTP connection to a WebSocket connection.

    Returns A WebSocket connection object for bidirectional communication.

    Important Notes
    • After upgrading, do not send an HTTP response. Use the WebSocket connection to communicate with the client instead.
    • The returned WebSocket connection is only valid during request handling. Do not store it or use it after the response cycle completes.

    TypedBox

    pub extenum TypedBox {
    }

    TypedKey

    type TypedKey[T]

    A typed key used to store and retrieve custom values from typed stores.

    TypedKey::TypedKey

    fn[T] TypedKey::TypedKey(name? : String, box~ : (T) -> TypedBox, unbox~ : (TypedBox) -> T?) -> TypedKey[T]

    Creates a fresh typed key with custom boxing and unboxing logic.

    TypedKey::name

    fn[T] TypedKey::name(self : TypedKey[T]) -> String

    Returns the debug name associated with this key.

    default_handler

    async fn default_handler(Request, Responder) -> Unit

    The default handler that responds with 404 Not Found.

    listen

    async fn listen(reuse_addr? : Bool, dual_stack? : Bool, host? : String, port? : UInt16) ->
    Server

    Creates an HTTP server listener bound to the given host (IP address only) and port.

    If host is the IPv6 wildcard address [::] and dual_stack is true (true by default), the server will work in dual stack mode, accepting connections from both IPv4 clients and IPv6 clients. The address of IPv4 clients are represented via IPv4-mapped IPv6 address.

    If host is not [::], dual_stack is ignored.

    If the port of host is zero, the server will be bound to a random port, assigned by the operating system. The actual listen address can be retrieved via .addr().

    If reuse_addr is true (true by default), the SO_REUSEADDR option will be enabled on the server, allowing in currently-in-use socket address (as long as there is no one else currently listening on the same address). This is useful for avoiding "address already in use" error.