@nwire/http
v0.9.2
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
HTTP wire for Nwire — Koa underneath, Zod schemas, OpenAPI 3.1 from the live wiring, Scalar docs.
httpInterface() is a small chainable builder that ties Zod-validated
routes to handler functions. Works three ways: standalone (bring your
own Container), with a Nwire app (endpoint().serve(app).serve(api)),
or as interop (api.compile() mounts on Express / Fastify / Koa / Nest
via the thin @nwire/http-* adapter packages).
pnpm add @nwire/http @nwire/endpoint zodQuick example — standalone
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();http://localhost:3000/openapi.json returns the live spec; /docs
serves Scalar UI from CDN; /healthz + /readyz come from the
endpoint layer.
Quick example — with a Nwire app
import { createApp } from "@nwire/forge";
import { httpInterface, post } from "@nwire/http";
import { endpoint } from "@nwire/endpoint";
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 ctx.resolve() automatically; no
.provide() needed when an app is served alongside.
Quick example — Express interop
import express from "express";
import { toExpress } from "@nwire/http-express";
const expressApp = express();
expressApp.use("/nwire", toExpress(api));
expressApp.listen(3000);Surface
| Export | Role |
| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------- |
| httpInterface(options?) | Builder. .use(mw) / .provide(container) / .wire(binding, handler) / .compile() / .toKoa(). |
| get / post / put / patch / del | Verb-builder route factories. Each takes RouteSchemas (params / query / body) + OpenAPI metadata. |
| defineCheck(name, fn) | Readiness probe (re-exported from @nwire/endpoint). |
| buildOpenApiDocument(api, info) | OpenAPI 3.1 emitter; auto-mounted by the builder when openapi.info is set. |
| scalarHtml(specUrl) | Scalar docs HTML for the /docs route. |
| attachLifecycle / HealthCheck / HealthConfig / ShutdownConfig | Re-exports from @nwire/endpoint for one-package consumers. |
Response shapes
Handlers return either a plain value (200 OK, JSON-serialized) or a tagged response:
return { id: "1", name: "Alice" }; // 200 OK
return { $status: 201, body: { id: "1" } }; // custom status
return { $status: 204 }; // no content
throw new NotFoundError("user", input.id); // serialized as JSON errorPer-route middleware
Middleware lives on the route binding so each route gets its own
http.request:<METHOD> <path> hook for taps + OTel spans:
.wire(
post("/orders", {
body: OrderInput,
middleware: [authenticate, requireRole("operator")],
}),
createOrderHandler,
)Related
@nwire/endpoint— wraps the interface in a Node process with probes + graceful shutdown.@nwire/http-express—toExpress(api)/fromExpress(mw)interop.@nwire/forge— suppliesdefineAction/defineResourcefor handlers that dispatch.@nwire/hooks— the dispatch substrate; globalhttp.request+ per-route hooks live here.
Status
v0.x — builder verbs (.use / .provide / .wire / .compile / .toKoa) and route-binding shape are locked. Per-route OpenAPI metadata is additive.
