Skip to main content

std.net — HTTP Server

Create a server, register routes, and listen. Each route maps a path to a handler(req) function. The engine calls handlers on background fibers when requests arrive.

Minimal REST API

var srv = std.net.http_server()
srv.routes([{
path: "/greet", handler: fn(req) {
var who = "world"
if (req.query.name != nil) { who = req.query.name } // /greet?name=Bob
return "hello, " + who // plain string → 200 text/plain
}
}, {
path: "/json", handler: fn(req) {
return {
status: std.net.http_codes.ok,
content_type: "application/json",
body: std.json.stringify({result: 42}),
}
}
}, {
path: "*", handler: fn(req) { // catch-all
return {
status: std.net.http_codes.not_found,
content_type: "application/json",
body: std.json.stringify({error: "nope"}),
}
}
}])

srv.listen(8200)
print("listening on", srv.port()) // actual port (useful when 0)
std.async.sleep(5000) // keep serving
srv.stop()

Handler input — the req object

FieldDescription
req.method"GET", "POST", …
req.path"/greet" (no query string)
req.queryParsed query object, e.g. {name: "Bob"} for ?name=Bob
req.headersRequest headers object
req.raw_headersRaw header block string
req.bodyRequest body string

Handler output — string or response object

Return a plain string for an HTTP 200 with text/plain:

handler: fn(req) { return "hello" } // 200 text/plain

Or return a response object for full control:

KeyPurposeDefault
statusHTTP status code200
content_typeContent-Type headertext/plain or guessed for file
headersExtra response headers object{}
bodyResponse body string""
filePath to a file to serve

File serving example:

// inside a handler:
return {file: "./public/index.html"} // content-type guessed from extension

Routing

  • First matching route wins — order your routes([...]) array from specific to generic.
  • path: "*" is the catch-all; place it last.
  • Registering routes replaces the previous table — call it once with the full array.

:::tip Parser note Avoid a trailing comma after a member whose value is a multi-line fn body — write the closing } of the handler directly followed by the object's }, as in the example above. A trailing comma there can trigger a parse edge case. :::

Lifecycle and errors

srv.listen(8200, "127.0.0.1") // port [, host] — a {cert, key} third argument serves TLS
print(srv.is_running()) // true
print(srv.port()) // actual port
srv.stop() // halt and close

print(srv.last_error()) // most recent background error or null
  • Handlers run on background fibers — the main script must sleep/await or the server exits with the script.
  • Handler exceptions and TLS handshake failures have no caller to throw to — they are stored per-server and readable via server.last_error() (also echoed to stderr). See Concurrency — Errors.
  • TLS: pass a {cert, key} third argument to listen() (PEM file paths) to serve https:// — requires an OpenSSL build, else UNSUPPORTED (see Overview).
  • Use port: 0 to let the OS pick a free port, then share srv.port() with clients.

See also