@orveth/server

Application runtime: the Orveth application class for Node.js HTTP—pattern routes, ordered middleware (global before route resolution), RequestContext, and JSON/text helpers backed by @orveth/http. Import from @orveth/server or orveth.

What ships in the public entrypoint

ExportDescription
OrvethApplication class wrapping http.createServer.
DEFAULT_MAX_REQUEST_BODY_BYTESDefault JSON body buffer limit (1 MiB) used when readJson omits maxBytes.
Handler, HttpMethod, Middleware, Next, RequestContext, RouteHandler, ListenOptionsRouting and handler typing surface (Handler aliases RouteHandler). ListenOptions types the object form of listen.

Orveth instance API

MethodBehavior
use(middleware)Appends global middleware executed in registration order before route matching (needed for CORS preflight and similar).
route(method, path, handler)Registers a route for any supported HTTP verb.
get|post|put|patch|delete|head|options(path, handler)Registers a route pattern for the corresponding HTTP verb. Patterns support :param segments and a trailing * wildcard (capture in ctx.params["*"]).
all(path, handler)Registers the handler for every supported HTTP method on the path.
toRequestListener()Node RequestListener for http.createServer or https.createServer (see HTTPS).
listen(port, hostname?)
listen({ port, hostname? })
Starts plain HTTP via http.createServer. Returns a promise resolving to http.Server. Hostname defaults to 0.0.0.0. Port must be an integer from 0 to 65535; invalid values throw ORVETH_LISTEN_INVALID_PORT.
app.ts
import { Orveth } from "orveth";

const app = new Orveth();
app.get("/health", (ctx) => ctx.ok({ ok: true }));

await app.listen(3000);
await app.listen({ port: 3000, hostname: "127.0.0.1" });

Listening (HTTP vs HTTPS)

listen is for HTTP only. For TLS, use @orveth/https (listenHttps(app, port, tls)) or https.createServer with app.toRequestListener().

Routing model

  • Matching uses the HTTP method plus a path pattern. Static segments match literally; :name captures a single path segment into ctx.params[name]; a trailing * captures the rest of the path into ctx.params["*"] (may be empty). Query strings are ignored for matching but available on ctx.query.
  • The first registered matching route wins for a given method. Registering the same method and path pattern twice throws OrvethError with code ORVETH_ROUTE_DUPLICATE.
  • When the path would match a route registered for another supported method, the runtime returns 405 with code ORVETH_HTTP_METHOD_NOT_ALLOWED and an Allow header derived from registered methods.
  • When no pattern matches any method, the runtime returns 404 with code ORVETH_HTTP_NOT_FOUND.
  • Incoming HTTP verbs the runtime does not support (outside GET/POST/PUT/PATCH/DELETE/HEAD/OPTIONS) receive 405 during method parsing.

Request context

Handlers and middleware receive a RequestContext exposing the raw Node IncomingMessage and ServerResponse, normalized method, path (pathname), url (may include query), params, query, headers, optional valid (from validation middleware), and helpers. Return ctx.ok(...) or ctx.json(...) from handlers for a concise style — await is optional.

MemberNotes
method, path, urlNormalized method; pathname only; raw request.url (often includes query).
paramsCaptured route segments (:id) and params["*"] for wildcards.
queryParsed query string (duplicate keys become string[]).
headersAlias of request.headers.
validOptional slot (for example valid.body after @orveth/validation-zod).
requestIdOptional correlation ID when using @orveth/security request-id middleware.
sendStatus(status)Async. Ends the response with a status and no body (after optional setHeader).
ok(data) / created(data) / noContent()Shorthand for JSON 200, JSON 201, and empty 204 responses.
setHeader(name, value)Sets an outgoing header before writing the body.
json(data, init?)Async. Sets JSON content type, serializes with JSON.stringify, ends the response.
text(body, init?)Async. Writes UTF-8 plain text with appropriate content type.
readJson(init?)Async. Buffers the body up to maxBytes (default DEFAULT_MAX_REQUEST_BODY_BYTES), parses JSON, returns unknown.
wasResponded()Returns true after a helper successfully commits headers and body.

Common readJson failures

  • HttpError 400 ORVETH_JSON_BODY_EMPTY when the body trims to an empty string.
  • HttpError 400 ORVETH_JSON_BODY_INVALID when JSON.parse fails.
  • HttpError 413 ORVETH_REQUEST_BODY_TOO_LARGE when the stream exceeds the byte budget (the socket is paused, not destroyed, so you can still emit a response).

Middleware pipeline

Global middleware (app.use) runs first, before the router runs. After a route matches, any route-level middleware registered with that path runs, then the handler. Each function receives (ctx, next). Calling next() continues the chain. Omitting next() without terminating the response leaves the request hanging.

Calling next() more than once in the same middleware stage rejects with OrvethError code ORVETH_MIDDLEWARE_NEXT_TWICE.

src/middleware/log.ts
import type { Middleware } from "@orveth/server";

export const logRequests: Middleware = async (ctx, next) => {
  const started = performance.now();
  await next();
  const ms = performance.now() - started;
  console.info(`[${ctx.method} ${ctx.path}] ${ms.toFixed(1)}ms`);
};

Error mapping

Exceptions thrown from middleware or route handlers pass through writeErrorResponse:

  • HttpError → JSON body from toNormalized() with the error's statusCode.
  • Other OrvethError subclasses → HTTP 500 with normalized payload (status code is not taken from the instance unless it is an HttpError).
  • Unknown values → HTTP 500 with a generic internal error envelope.

If the handler stack returns without committing a response (and the socket is still writable), the runtime throws HttpError with code ORVETH_NO_RESPONSE.

End-to-end example

src/app.ts
import { Orveth } from "@orveth/server";

const app = new Orveth();

app.use(async (ctx, next) => {
  const start = performance.now();
  await next();
  const durationMs = performance.now() - start;
  ctx.response.setHeader("server-timing", `app;dur=${durationMs.toFixed(1)}`);
});

app.get("/health", (ctx) => ctx.ok({ ok: true }));

app.post("/echo", async (ctx) => {
  const body = await ctx.readJson();
  return ctx.json({ received: body });
});

await app.listen(3000);

Current limitations

  • No nested routers or mount prefixes (flat pattern table per app).
  • No static asset hosting, HTTP/2 APIs, or WebSocket integration inside this package.
  • Large uploads should use streaming-oriented designs; readJson buffers the entire body into memory up to the configured cap.
  • Authentication, sessions, and rate limiting are application concerns—use middleware and your chosen libraries; Orveth does not ship a full auth product.