Handler API
Site code is a JavaScript ES module. This page is the reference for the handler contract and the Web-style globals available to it. The capabilities on ctx (database, storage, secrets, mail, network) are in Context, the optional modules in SDK modules. Editor types are in sdk/microhost.d.ts.
Status. The SDK surface is shared by all three engines (Wasm, native, Linux worker). The cross-engine conformance suite (
crates/engine-api/conformance, 48 scenario files) covers the handler contract,Request,Response, status and redirect rules, header rules, URL,TextEncoder,crypto.getRandomValues, import rules, limits and capability absence. It does not exercisemicrohost/sql,microhost/dbormicrohost/session, and notfetchor mail.microhost serve --devserves static files only; handlers answer 501 there.
The handler
export default async function handle(request, ctx) {
return Response.json({ method: request.method, path: new URL(request.url).pathname });
}
- The module's default export must be a function. Declare it
async(or return a promise). It must resolve to aResponse. - Resolving to anything else (a string, a plain object,
undefined) is an invalid response: HTTP 502. - A thrown exception or rejected promise is HTTP 500. The message and stack never reach the visitor.
- The engine creates a fresh runtime for each execution. Module-level variables are not shared between requests.
- Await all work before returning. There is no
waitUntil, no background job, no timers. - A timeout terminates execution without running
catchorfinally.
Request
request is created by the host. Request cannot be constructed by site code.
| Member | Value |
|---|---|
method |
Uppercase HTTP method. |
url |
Absolute URL https://<host header><decoded path><?query>. The scheme is always https. |
headers |
A Headers with lowercase names. |
params |
Frozen, null-prototype object of route captures; empty for exact and convention routes. See Routing. |
text() |
Promise of the body decoded as UTF-8. |
json() |
Promise of the parsed body. Throws on an empty or invalid body, so wrap it in try. |
arrayBuffer() |
Promise of an ArrayBuffer. |
Bodies are fully buffered and limited to 1 MiB by default (oversize requests are answered 413 by the edge and never reach your code). Methods without a body give an empty string.
Headers your code sees: the edge removes hop-by-hop headers, content-length, x-forwarded-*, forwarded and x-real-ip. There is no way to read the client IP. host and cookie are present. Header values that are not plain visible ASCII are dropped.
Request has no path or query members. Use new URL(request.url) (pathname, search).
Response
new Response(body?, { status?, headers? })
Response.json(value, init?)
Response.html(html, init?)
Response.redirect(url, status)
bodyis a string, aUint8Array,nullor omitted. Any other type throwsTypeError.statusdefaults to 200 and must be an integer from 200 to 599; otherwiseRangeError.- A string body gets
content-type: text/plain;charset=UTF-8unless you set one. AUint8Arraybody gets no content type. Response.jsonserializes withJSON.stringifyand always setscontent-type: application/json(it throwsTypeErrorfor a value that cannot be serialized, such asundefined).Response.htmlalways setscontent-type: text/html; charset=utf-8andx-content-type-options: nosniff. It does not sanitize anything: escape untrusted text yourself.Response.redirect(url, status)setsLocation.urlmay be a string orURL.statusis required and must be 301, 302, 303, 307 or 308 (otherwiseRangeError).- The only readable member is
response.headers, a mutableHeaders. There are nostatus,body,okor body-reader members on a siteResponse.
Host-side response checks (a violation is an invalid response, HTTP 502): header names must be legal tokens, values must not contain CR or LF, a Set-Cookie value is at most 4096 bytes, and the body must fit the response limit (4 MiB by default; above it the execution fails with HTTP 500). The host drops hop-by-hop headers (connection, keep-alive, proxy-*, te, trailer, transfer-encoding, upgrade) and content-length and recomputes framing.
Headers
new Headers(init?) accepts another Headers, an array of [name, value] pairs, or a plain object. Methods: get, has, set, append, delete. Names are case-insensitive. Values are converted with String().
appendon an existing header joins values with", ", exceptSet-Cookie.Set-Cookieis kept as separate values. Eachappend("Set-Cookie", value)becomes its own header on the wire.get("set-cookie")returns the values joined with", "for convenience only.setreplaces all values of a name.- There is no iteration (
entries,forEach,keys) and nogetSetCookie.
const response = Response.redirect("/account", 303);
response.headers.append("Set-Cookie", "a=1; Path=/; Secure; HttpOnly");
response.headers.append("Set-Cookie", "b=2; Path=/; Secure; HttpOnly");
URL
new URL(input, base?) parses an absolute URL, or a relative one against base. It throws TypeError for an invalid URL. Read-only properties: href, origin, protocol, host, hostname, pathname, search, hash; toString() returns href. There is no URLSearchParams and no searchParams: search is the raw query string. If you parse queries yourself, decide how to treat repeated keys, +, and malformed percent escapes.
Text and bytes
new TextEncoder().encode(string?)returns aUint8Array(UTF-8).new TextDecoder().decode(bytes?)takes aUint8ArrayorArrayBuffer. UTF-8 only; no options.
Randomness and time
crypto.getRandomValues(typedArray)fillsUint8Array,Uint16Array,Uint32Array,Int8Array,Int16ArrayorInt32Array, at most 65,536 bytes per call (RangeErrorabove that,TypeErrorfor other types). It uses host randomness.- There is no
crypto.subtleand nocrypto.randomUUID.microhost/dbexportsid(), which builds a UUID v4 fromgetRandomValues. Math.random()on the Wasm engine is served from host randomness. Do not rely on it for secrets on any engine; usegetRandomValues.Dateworks with the host clock.microhost/dbexportsnow()(an ISO UTC string). Database-side expiry (sessions) uses the database clock instead.
Console
console.log, info, warn, error write to the site log, never to the visitor. Strings print as is, Error objects as Name: message, other objects as JSON, arguments joined by spaces. Output is capped at 64 KiB per execution; later lines are dropped. Values your code obtained from ctx.env.get are redacted from console output. Do not log request bodies or credentials.
Modules
Handlers are ES modules with static imports:
import { escapeHtml } from "../lib/html.js"; // relative, ends in .js, stays in the release
import { users } from "microhost/db"; // reserved SDK module
Rules (details in Site layout): relative specifiers only (./ or ../), they must end in .js, and they must stay inside the release outside public/. Importing a public/ file fails with HTTP 403. Dynamic import(), eval and new Function are not available (a dynamic import fails with HTTP 500). Bare specifiers other than the four reserved SDK names fail. At most 256 modules and 8 MiB of module source per execution.
Not available
These are absent. Using them gives ReferenceError or undefined; do not polyfill them.
- Node and browser objects:
process,Buffer,require,module,global,window,document,localStorage. - Timers:
setTimeout,setInterval,setImmediate. - Network primitives other than
fetch:XMLHttpRequest,WebSocket,EventSource, raw sockets. AbortController,AbortSignal.- Streams:
ReadableStream,WritableStream, streaming bodies. URLSearchParams,FormData,Blob,File; no automatic multipart or form parsing (send JSON and userequest.json(), or parsetext()yourself).crypto.subtle,crypto.randomUUID.eval,new Functionand every other source-string evaluator; dynamicimport().- A built-in cookie parser, template engine or authentication layer.
microhost/sessionprovides sessions explicitly. - Interactive database transactions across calls (use
ctx.db.transaction).
Errors thrown by host calls
Host calls (ctx.db, ctx.storage, ctx.env, ctx.mail, fetch) reject with an Error whose code is one of CAPABILITY_DENIED, QUOTA_EXCEEDED, INVALID_INPUT (a TypeError), HOST_UNAVAILABLE, HOST_INTERNAL. Messages are generic. SDK modules add their own lowercase codes. Full tables are in Limits and errors.