One handler, every transport
A handler is a plain typed function. Wire the same one to an HTTP route, a queue worker, a cron job, and an MCP tool. Adding a transport is a line of wiring, never a rewrite.
Write your backend logic once. Run it under HTTP, a queue, cron, or MCP — with auth, storage, email, a live trace view, and production defaults built in. The deep stuff is there when you need it, never in your way when you don't.
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();One typed route, graceful shutdown, and K8s probes — no extra setup. Just a handler on a transport.
This is the seam Nwire is built around. Write the operation once, then expose it through as many transports as make sense. The logic does not change:
import { createApp } from "@nwire/app";
import { endpoint } from "@nwire/endpoint";
import { post } from "@nwire/wires/http";
import { queue } from "@nwire/wires/queue";
import { httpKoa } from "@nwire/koa";
import { queueInMemory } from "@nwire/queue";
import { z } from "zod";
const app = createApp({ appName: "billing" });
// one handler — a plain typed function
const charge = async (input: { orderId: string }) => ({ charged: input.orderId });
app.wire(post("/orders/:orderId/charge", { params: z.object({ orderId: z.string() }) }), charge);
app.wire(queue("orders.charge", { input: z.object({ orderId: z.string() }) }), charge);
await endpoint("billing", { port: 3000 })
.use(httpKoa()) // serves the HTTP route
.use(queueInMemory()) // serves the same handler as a queue worker
.mount(app)
.run();POST /orders/o-1/charge and an enqueue to orders.charge run the identical handler. Swap queueInMemory() for @nwire/bullmq to back it with Redis — the handler still doesn't change.
→ Walk through it step by step · Why it's shaped this way · Get started
Pre-release, MIT-licensed, driving production at 200apps. Some APIs may still change before 1.0. Source and issues on Bitbucket.