moonback

A web backend framework for MoonBit

moonbit
web
http
server
async
Download zip
Version
0.8.2
License
Apache-2.0
Last updated
23 hours ago
Downloads
3

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.

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.