This version of the module is deprecated: Use moonbitlang/moonback instead

    moonback

    A web backend framework for MoonBit

    Download zip
    Author
    Version
    0.5.3
    License
    Apache-2.0
    Last updated
    4 months ago
    Downloads
    19K

    #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.19.2",
    "hackwaly/moonback@0.5.3",
    }

    MoonBack is intended for the native target:

    preferred_target = "native"

    #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()

    #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::build(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",
    "hackwaly/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
    }

    extenum @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().

    extenum @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 are independent apps. By default, the parent app owns and closes the mounted app.

    #Development

    Useful commands:

    moon check moon test moon fmt moon info

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

    #License

    Apache-2.0

    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 {
    fn write_to(Self, HtmlBuilder) -> Unit
    }

    DependencyError

    pub suberror DependencyError {
    MissingDependency(String)
    DuplicateDependency(String)
    }

    RouterError

    pub suberror RouterError {
    RouteConflict(StringView)
    }

    App

    pub struct App {
    // private fields
    }
    async fn App::App(root : Module, config? : Config) -> App

    A configured application that can handle requests and serve listeners.

    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
    }
    fn Config::Config(max_connections? : Int, stop_timeout? : Double, trust_proxy? : Bool, custom? : ArrayView[CustomConfigEntry]) -> Config

    Runtime configuration for an application instance.
    impl Default for Config

    Config::custom

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

    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

    pub struct Headers {
    // private fields
    }
    fn Headers::Headers() -> Headers

    impl Default for 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_mut

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

    Html

    pub struct Html {
    // private fields
    }
    fn Html::Html(raw~ : String) -> Html

    Html::build

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

    Html::escape

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

    HtmlBuilder

    pub struct HtmlBuilder {
    // private fields
    }
    fn HtmlBuilder::HtmlBuilder() -> HtmlBuilder

    HtmlBuilder::html

    fn HtmlBuilder::html(self : HtmlBuilder) -> Html

    HtmlBuilder::write

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

    HtmlBuilder::write_string

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

    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

    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) -> Unit raise RouterError

    Mounts another app under path. 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))

    MutHeaders

    pub struct MutHeaders {
    // private fields
    }
    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_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

    pub struct TypedKey[T] {
    // private fields
    }
    fn TypedKey::TypedKey(name? : String, box~ : (T) -> TypedBox, unbox~ : (TypedBox) -> T?) -> TypedKey[T]

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

    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.

    Powered by MoonBit

    Site sourceReport issueModulesSkillsBuild queueStatistics

    © 2026 mooncakes.io