Web Servers
ngn includes a built-in web server, as well as Request and Response models for APIs and streaming. Default port is 3000.
Request
Section titled “Request”Properties
Section titled “Properties”method: The HTTP method of the requesturl: The URL of the requestprotocol: Whether HTTP or HTTPs was usedhost: The host the client used to make the requestpath: The path of the requestquery: The query string of the requestparams: Query parameters as aMap<string, string>headers: The headers of the requestbody: The body of the requestip: The client’s IP addresscookies: The cookies sent with the request
Methods
Section titled “Methods”clone(): Creates a newRequestobject with the same propertiestext(): Parses the body as a string, returns astringjson(): Parses the body as JSON, returns a Result enumformData(): Parses URL-encoded body, returns aMap<string, string>
Response
Section titled “Response”Properties
Section titled “Properties”status: The HTTP status code - default is 200statusText: The HTTP status text - default is ""ok: A boolean indicating whether the response status code is in the range 200-299headers: The headers to include in the responsebody: The body of the response - default is ""
Methods
Section titled “Methods”text(): Parses the body as a string, returns astringjson(): Parses the body as JSON, returns a Result enumformData(): Parses URL-encoded body, returns aMap<string, string>
export default
Section titled “export default”fn handler(req: Request): Response { return Response { body: "Hello from ngn!" }}
export default { fetch: handler }ngn run api.ngnimport { serve } from "tbx::http"
fn handler(req: Request): Response { match (req.path) { "/" => return Response { body: "Hello!" }, "/api" => return Response { body: json.stringify({ ok: true }) }, _ => return Response { status: 404, body: "Not found" } }}
fn main() { serve(handler, { port: 5173 })}StreamingResponse
Section titled “StreamingResponse”Send HTTP responses with chunked transfer encoding, allowing data to be streamed to the client as it becomes available. Perfect for LLM inference, large file downloads, or any scenario where you want to send data incrementally.
fn handler(req: Request): StreamingResponse { const chunks = channel<string>()
// Background thread produces data thread(|| { chunks <- "First chunk\n" sleep(500) chunks <- "Second chunk\n" sleep(500) chunks <- "Done!\n" chunks.close() // Signals end of stream })
return StreamingResponse { headers: { "Content-Type": "text/plain" }, body: chunks }}
export default { fetch: handler }Properties
Section titled “Properties”status: The HTTP status code - default is 200headers: The headers to include in the responsebody: Achannel<string>that produces chunks. Close the channel to end the stream.
SseResponse (Server-Sent Events)
Section titled “SseResponse (Server-Sent Events)”Server-Sent Events (SSE) streams a sequence of events over a single HTTP response. This is useful for realtime updates (notifications, progress updates, model inference tokens, etc.).
SSE works over both HTTP and HTTPS in ngn.
fn handler(req: Request): SseResponse { const events = channel<SseMessage>()
thread(|| { events <- SseEvent { data: "Hello", event: "hello", id: "", retryMs: 0, comment: "" } sleep(500)
// Send raw strings as event data events <- "World" sleep(500)
// Send raw objects shaped like SseEvent events <- { data: "Hello", event: "hello", id: "", retryMs: 0, comment: "" }
events.close() })
return SseResponse { status: 200, headers: { "Access-Control-Allow-Origin": "*" }, body: events, keepAliveMs: 15000, }}
export default { fetch: handler }Properties
Section titled “Properties”status: The HTTP status code - default is 200headers: The headers to include in the responsebody: Achannel<SseMessage>that can send either astring(treated as event data), anSseEvent, or a raw object that represents anSseEventkeepAliveMs: Optional keepalive interval (in milliseconds). If > 0, the server periodically sends: keepalivecomments while idle.
SseEvent
Section titled “SseEvent”data: Event payload (string). Newlines are sent as multipledata:lines.event: Optional event name (maps to the SSEevent:field)id: Optional event id (maps to the SSEid:field)retryMs: Optional client reconnection hint (maps to the SSEretry:field)comment: Optional comment line (maps to the SSE: ...field)
WebSocketResponse
Section titled “WebSocketResponse”WebSockets provides a full-duplex channel between a client and your server over a single upgraded HTTP connection.
In ngn, a WebSocket connection is represented by two channels:
recv: messages from the client (client -> server)send: messages to the client (server -> client)
Notes:
- supports
string(text frames) andbytes(binary frames) - i.e.WsMessagetype - no subprotocol selection
fn handler(req: Request): WebSocketResponse { const recv = channel<WsMessage>() const send = channel<WsMessage>()
// Echo everything back thread(|| { for (msg in <-? recv) { send <- msg } send.close() })
return WebSocketResponse { recv, send }}
export default { fetch: handler }Properties
Section titled “Properties”headers: The headers to include in the 101 Switching Protocols response (optional)recv: Achannel<WsMessage>that receives client messages. It is closed when the client disconnects.send: Achannel<WsMessage>used to send messages to the client. Close it to close the websocket.