@nwire/koa
v0.15.1
Published
Nwire — Koa-backed HTTP adopter. Consumes wires from @nwire/wires/http and serves them as Koa routes under endpoint lifecycle.
Readme
@nwire/koa
The canonical HTTP adopter — a Koa server that consumes Nwire wires.
pnpm add @nwire/koa @nwire/app @nwire/endpoint @nwire/wiresQuick start
import { createApp } from "@nwire/app";
import { endpoint } from "@nwire/endpoint";
import { post } from "@nwire/wires/http";
import { httpKoa } from "@nwire/koa";
import { z } from "zod";
const app = createApp({ appName: "api" });
app.wire(post("/hello", { body: z.object({ name: z.string().min(1) }) }), async (input) => ({
message: `Hello, ${input.name}!`,
}));
await endpoint("api", { port: 3000 })
.use(httpKoa({ prefix: "/api" }))
.mount(app)
.run();Config
httpKoa({
prefix: "/api", // mounted on every wired route
bodyParser: { jsonLimit: "1mb" },
middleware: [requireUser], // adopter-wide Koa middleware run before every wire
logger: myLogger, // defaults to ConsoleLogger
});Security headers, CORS, and correlation are the endpoint's concerns, applied to every surface at once:
endpoint("api", { port: 3000, http: { cors: { origin: ["https://app.example"] } } })
.use(httpKoa({ prefix: "/api" }))
.mount(app)
.run();Per-route middleware
Wires can carry route-scoped middleware. httpKoa runs them after the
adopter-wide chain and before the handler.
import { post } from "@nwire/wires/http";
app.wire(
post("/admin/users", {
body: UserBody,
middleware: [requireRole("admin")],
}),
createUser,
);Handler shape
Two shapes are supported and route to the same dispatch:
// Foundation HTTP — plain (input, ctx)
app.wire(post("/order", { body: OrderBody }), async (input, ctx) => {
const repo = ctx.resolve<OrderRepo>("orders");
return repo.create(input);
});
// Forge HandlerDefinition — routed through runtime.execute
const placeOrder = defineHandler(placeOrderAction, async (input, ctx) => {
/* ... */
});
app.wire(post("/order", { body: OrderBody }), placeOrder);The adopter detects forge handlers (.run + runtime.execute present)
and routes them through the runtime so action.before/after hooks,
retries, telemetry, and per-action middleware all fire. Plain handlers
take the direct path with a minimal ctx (resolve, logger, koa,
envelope).
Response shaping
| Handler returns | HTTP response |
| ---------------------------------------------- | ------------------------------- |
| { $kind: "response-instance", status, body } | status, JSON body |
| { $status, body } (legacy envelope) | $status, JSON body |
| anything else | 200, JSON serialized verbatim |
| validation throws | 400, structured error JSON |
| handler throws defineError-shaped | error.status, structured JSON |
Testing
adapter.koa() exposes the underlying Koa app for in-process testing
via supertest or simulateRequest from @nwire/test-kit:
const koa = httpKoa({ port: 0 });
const running = await endpoint("test", { ... }).use(koa).mount(app).run();
const res = await simulateRequest(koa.koa(), {
method: "POST",
path: "/hello",
body: { name: "Alice" },
});adapter.port() returns the bound port (useful when port: 0).
Related
@nwire/endpoint— the lifecycle host@nwire/wires—get/post/put/patch/delbinding factories@nwire/express— the Express-flavored adopter
