The endpoint
An endpoint is the running process. It's where your app meets the outside world: it opens the ports, mounts the transports, and owns the lifecycle — boot, serve, drain, stop. A handler is logic; an endpoint is the thing that's actually running when you node dist/main.js.
import { createApp } from "@nwire/app";
import { endpoint } from "@nwire/endpoint";
import { get } from "@nwire/wires/http";
import { httpKoa } from "@nwire/koa";
const app = createApp({ appName: "hello" });
app.wire(get("/hello"), async () => ({ message: "hello world" }));
await endpoint("hello", { port: 3000 }).use(httpKoa()).mount(app).run();That one chain is the whole shape: name the process, use the transports that should serve it, mount the app whose handlers they call, and run.
What you get for free
run() isn't just "listen on a port." An endpoint is production-shaped on the first line, before you configure anything:
- Graceful drain. On
SIGTERM(the signal Kubernetes, Docker, and most process managers send to stop you) the endpoint stops accepting new work, lets in-flight requests finish, disposes plugins in reverse order, then exits. No half-finished requests cut off mid-flight. - Health probes. HTTP transports expose liveness and readiness endpoints, so an orchestrator can tell "the process is up" from "the process is ready to serve" — and won't route traffic to a pod that's still booting or already draining.
- Safe errors. A thrown
Errorthat isn't a typeddefineErroris redacted to an opaqueinternal_errorbefore it leaves the process — a driver message or stack trace can't escape to a caller by accident.
These aren't a "production hardening" chapter you graduate to. They're the default the first run() already gives you.
Mounting transports
The verbs on an endpoint are a fixed, small set — .use a transport, .mount an app, .run the process. You don't learn a new surface per transport; you use a different adapter:
await endpoint("orders", { port: 3000 })
.use(httpKoa()) // serve wired HTTP routes
.use(queueInMemory()) // serve wired queue topics
.use(mcpAdapter()) // expose wired actions as MCP tools
.mount(app)
.run();Each adapter reads the app's wires and serves the ones it understands: the HTTP adapter serves get/post/… bindings, the queue adapter serves queue(...) bindings, and so on. The same handler can be wired to several of them — that's the seam the whole framework is built around.
Endpoint vs app
A clean split, worth holding onto:
- The app is your domain made runnable — handlers, plugins, container, events. It knows nothing about ports or processes.
- The endpoint is the deployment — which transports are mounted, which port, how the process drains. It knows nothing about your domain.
That's why the same app runs as a single HTTP service, a queue worker, or both at once with no change to a handler: you're changing the endpoint, not the app. It's also how a monolith splits into services later — you compose the same apps under different endpoints (see topology).
See also
endpoint()+ lifecycle reference — every option and the full boot/drain sequence.- Production defaults — the safe-by-default posture in one place.
- Multi-transport — the same handler on HTTP, a queue, and MCP.