Web Standards
Built on srvx — every handler receives a real Request and returns whatever toResponse() can convert: string, object, stream, Response.
A minimalistic, runtime-agnostic HTTP framework. Return-based handlers, Web Standards everywhere — Node, Bun, Deno, Cloudflare, or any Fetch-ready runtime.
import { App, defineCoreHandler, serve } from 'routup';
const app = new App();
app.get('/users/:id', defineCoreHandler(
(event) => ({ id: Number(event.params.id) })
));
serve(app, { port: 3000 });Registered routes
/userslist users/users/:idshow user/userscreate user/files/*splatserve fileSend a request
Booting app.fetch()…
Each keystroke re-runs app.fetch() against a real Request. Same model that ships to Node, Bun, Deno, and Cloudflare Workers.
Routup keeps the surface narrow — App, Handler, Event — and lets the runtime, the Web platform, and tree-shaking do the rest.
Built on srvx — every handler receives a real Request and returns whatever toResponse() can convert: string, object, stream, Response.
Conditional exports auto-pick the right adapter for Node, Bun, Deno, Cloudflare Workers, Service Workers, or any Fetch-ready runtime.
No res.send(). Return a value; the pipeline picks the Content-Type, sets ETag/304, and ships the response — async or sync, your call.
Call event.next() to wrap downstream handlers. Mutate event.response before, transform the resolved value after.
Install reusable plugins with name + version + dependencies. Subscribe to request, response, and error lifecycle events.
Tiny standalone functions — getRequestIP, sendRedirect, setResponseCacheHeaders. Import only what you use; the rest is gone.
One App, six entry points. Conditional exports route the import to the adapter that matches the current runtime — no plugin, no platform branch in your code.
import { serve, toNodeHandler } from 'routup/node'srvx Node adapter plus a toNodeHandler() bridge for Express/Connect-style middleware stacks.
import { serve } from 'routup/bun'Bun.serve() under the hood — start in milliseconds, single binary, no transpile step.
import { serve } from 'routup/deno'Deno.serve() with permission boundaries and built-in TypeScript — same App, same handlers.
import { App } from 'routup/cloudflare'Edge-deployable. Export app.fetch as the Worker entry — no server, no cold start.
import { App } from 'routup/generic'Any host that speaks Web Standards. Wire app.fetch to whatever provides Request and Response.
Three steps. No decorators, no schema DSL, no per-runtime build target.
npm install routuproutup/node
Two helpers turn routup into an incremental migration. Wrap an existing compression(), cors(), or passport.authenticate() chain with fromNodeHandler(). Mount the router inside any http.Server with toNodeHandler().
// migrate.ts
import http from 'node:http';
import compression from 'compression';
import { App, defineCoreHandler } from 'routup';
import { toNodeHandler, fromNodeHandler } from 'routup/node';
const app = new App();
// 1. Wrap an Express/Connect middleware as a routup handler.
app.use(fromNodeHandler(compression()));
// 2. Add a return-based handler alongside it.
app.get('/health', defineCoreHandler(() => ({ ok: true })));
// 3. Mount the whole app inside a plain Node http.Server.
http.createServer(toNodeHandler(app)).listen(3000);