@nwire/http
v0.7.1
Published
Nwire — HTTP transport. httpInterface() builds a Koa app via the 6-verb InterfaceBuilder (.use / .wire / .from / .mount / .run / .boot), adds default middleware (cors, bodyparser, error envelope, healthz), seeds envelope from headers, graceful shutdown.
Readme
@nwire/http
Typed HTTP transport for Nwire — Koa under the hood, Zod schemas, OpenAPI 3.1, Scalar docs, graceful shutdown.
What it is
A small chainable builder, httpInterface(), that ties Zod-validated routes
to handler functions. Works three ways:
- Standalone — bring your own
Containervia.provide(container), mount on@nwire/endpoint, done. No forge, no app. - With a Nwire app —
endpoint().serve(app).serve(api)and the app's container fulfils handler ctx automatically. - Interop —
api.compile()returns a(req, res) => voidso the same interface mounts inside an existing Express, Fastify, Koa, or Nest host via the thin adapter packages (@nwire/http-express, …).
Every route's input schema produces both runtime validation and an OpenAPI 3.1 operation; the spec is generated from the live wiring, not codegen.
Install
pnpm add @nwire/http @nwire/endpoint zodQuick start
import { httpInterface, get, post } from "@nwire/http";
import { endpoint } from "@nwire/endpoint";
import { z } from "zod";
const api = httpInterface({ prefix: "/api/v1" })
.wire(get("/users/:id", { params: z.object({ id: z.string() }) }), async ({ input }) => ({
id: input.id,
name: "Alice",
}))
.wire(post("/users", { body: z.object({ name: z.string() }) }), async ({ input }) => ({
$status: 201,
body: { id: "1", name: input.name },
}));
await endpoint("api", { port: 3000 }).serve(api).run();Hit http://localhost:3000/openapi.json for the spec, /docs for the
Scalar UI, /healthz and /readyz for the liveness probes.
With a Nwire app
import { createApp } from "@nwire/forge";
import { httpInterface } from "@nwire/http";
import { endpoint } from "@nwire/endpoint";
import { ordersModule } from "./orders";
const app = createApp({ modules: [ordersModule] });
const api = httpInterface({ prefix: "/api/v1" }).wire(
post("/orders", { body: OrderInput }),
async ({ input, dispatch }) => dispatch("orders.create", input),
);
await endpoint("api", { port: 3000 }).serve(app).serve(api).run();The app's Container fulfils handler ctx.resolve() calls automatically;
no .provide(container) needed when you serve an app.
API surface
httpInterface(options?)— the builder. Chainable:.use(mw)/.provide(container)/.wire(binding, handler)/.compile()/.toKoa().get/post/put/patch/del— verb-builder route factories. Each accepts an optionalRouteSchemas(params,query,body) plus OpenAPI metadata.defineCheck(name, fn)— health/readiness check, plumbed byendpoint()to/healthzand/readyz.buildOpenApiDocument(api, info)+scalarHtml(specUrl)— OpenAPI generation + docs HTML. Mounted automatically by the builder whenopenapi.infois configured.
Response shapes
Handlers return either:
// plain value → 200 OK, JSON-serialized
return { id: "1", name: "Alice" };
// tagged response → custom status + body
return { $status: 201, body: { id: "1" } };
return { $status: 204 };
return { $status: 404, body: { error: "not_found" } };
// thrown error → middleware envelopes as JSON
throw new NotFoundError("user", input.id);