xil
A typed router, a per-request context, unified errors, and optional modules for auth, uploads, rate limiting, and OpenAPI — all fully typed, none of it hidden behind magic.
A first route
Section titled “A first route”import { createApp, NotFound, Router } from "@architectine/xil";import { z } from "zod";
const orders = new Router("/orders");
orders.get("/:id").handle((ctx) => { const order = db.orders.get(ctx.params.id); // ctx.params.id: string, inferred from ":id" if (!order) throw new NotFound(`Order ${ctx.params.id} does not exist`); return order; // -> 200 JSON});
orders .post("/") .body(z.object({ total: z.number().positive() })) .handle((ctx) => db.orders.create({ total: ctx.body.total })); // ctx.body.total: number, validated // -> 201 JSON
const app = createApp();app.mount(orders);await app.listen(3000);Every thrown error and every validation failure renders through the same shape:
{ "code": "VALIDATION", "message": "Invalid body", "details": { "in": "body", "issues": [] }, "requestId": "…" }Continue with Your first app for the full walkthrough.
Why xil
Section titled “Why xil”Express is the transportapp.express is always the real Express instance. Any (req, res, next) middleware from the ecosystem works unchanged.
Typed end to endPath params inferred from the route string, Standard Schema validation for query/body/headers, Middleware narrowing across the chain.
Nothing implicit at bootModules are constructed by you and passed where needed. Missing config or adapter methods fail at startup, listed together.
One error shape everywhereThrow an HttpError, a validation failure, or a plain Error — every one renders as the same envelope.
Bring your own schema libraryZod, Valibot, and ArkType all work through Standard Schema. xil depends on none of them.
Optional modules, not a plugin systemAuth, uploads, rate limiting, and OpenAPI live at their own subpaths. Import what you use; nothing registers itself.