Routup is a minimalistic, runtime-agnostic HTTP routing framework for Node.js, Bun, Deno, and Cloudflare Workers.
Handlers return values directly — routup converts them to Web Response objects automatically.
Table of Contents
npm install routup --save- 🚀 Runtime agnostic — Node.js, Bun, Deno, Cloudflare Workers
- 🌐 Web-standard APIs — built on
Request/Responsefor portability - 📝 Return-based handlers — return strings, objects, streams, or
Responsedirectly - ✨ Async middleware — onion model with
event.next() - 📌 Lifecycle hooks —
request,response,errorfor cross-cutting concerns - 🔌 Plugin system — extend with reusable, installable plugins
- 🧰 Tree-shakeable helpers — import only what you use
- 📁 Nestable routers — modular route composition
- 👕 TypeScript first — fully typed API with generics
- 🤏 Minimal footprint — small core, no bloat
To read the docs, visit https://routup.net
Handlers receive an event and return a value. Routup converts the return value to a Web Response automatically.
Shorthand
import { Router, defineCoreHandler, defineErrorHandler, serve } from 'routup';
const router = new Router();
router.get('/', defineCoreHandler(() => 'Hello, World!'));
router.get('/greet/:name', defineCoreHandler((event) => `Hello, ${event.params.name}!`));
router.use(defineErrorHandler((error) => ({ error: error.message })));
serve(router, { port: 3000 });Verbose
import { Router, defineCoreHandler, serve } from 'routup';
const router = new Router();
router.use(defineCoreHandler({
path: '/',
method: 'GET',
fn: () => 'Hello, World!',
}));
router.use(defineCoreHandler({
path: '/greet/:name',
method: 'GET',
fn: (event) => `Hello, ${event.params.name}!`,
}));
serve(router, { port: 3000 });| Return type | Response |
|---|---|
string |
text/plain |
object / array |
application/json |
Response |
Passed through as-is |
ReadableStream |
Streamed to client |
Blob |
Sent with blob's content type |
null |
Empty response (status from event.response) |
Middleware calls event.next() to continue the pipeline:
router.use(defineCoreHandler(async (event) => {
console.log(`${event.method} ${event.path}`);
return event.next();
}));Routup runs on Node.js, Bun, Deno, and Cloudflare Workers. In most cases, import from routup:
import { Router, defineCoreHandler, serve } from 'routup';
const router = new Router();
router.get('/', defineCoreHandler(() => 'Hello, World!'));
serve(router, { port: 3000 });For runtime-specific APIs (e.g. toNodeHandler), use the corresponding entrypoint like routup/node.
Scaffold a new project from any starter in routup/templates with degit:
npx degit routup/templates/node-api my-app| Template | Runtime | Highlights |
|---|---|---|
| node-api | Node.js >=22 | JSON API with @routup/body |
| cloudflare-worker | Cloudflare Workers | Configured with wrangler |
| bun-decorators | Bun | Class-based routing via @routup/decorators |
Routup is minimalistic by design. Plugins extend the framework with additional functionality.
| Name | Description |
|---|---|
| assets | Serve static files from a directory |
| basic | Bundle of body, cookie, and query plugins |
| body | Read and parse the request body |
| cookie | Read and parse request cookies |
| cors | Cross-Origin Resource Sharing (CORS) middleware |
| decorators | Class, method, and parameter decorators |
| i18n | Translation and internationalization |
| logger | HTTP request logger with morgan-compatible tokens and presets |
| prometheus | Collect and serve Prometheus metrics |
| query | Parse URL query strings |
| rate-limit | Rate limit incoming requests |
| rate-limit-redis | Redis adapter for rate-limit |
| swagger-ui | Mount swagger-ui-dist on any path |
How routup stacks up against other popular Node.js routing frameworks. This is a best-effort summary; check each project's docs for the full picture.
| routup | Hono | Express | Fastify | |
|---|---|---|---|---|
| Runtimes | Node, Bun, Deno, Cloudflare, Service Worker | Node, Bun, Deno, Cloudflare, Lambda, Vercel | Node | Node |
Web-standard Request / Response |
✅ | ✅ | ❌ | ❌ |
| Return-based handlers | ✅ | ✅ | ❌ | ❌ |
| TypeScript-first | ✅ | ✅ | community types | ✅ |
| Tree-shakeable helpers | ✅ | ✅ | ❌ | ❌ |
Onion middleware (next()) |
✅ | ✅ | linear next() |
lifecycle hooks |
| Class-based routes (decorators) | ✅ via plugin | ❌ | ❌ | ❌ |
| Express middleware bridge | ✅ fromNodeHandler |
❌ | n/a | limited |
Per-handler timeout + AbortSignal |
✅ | ❌ | ❌ | server-level |
| Schema validation built-in | ❌ | ❌ | ❌ | ✅ |
Recorded with routup v5.1 on Node.js 24, May 2026. autocannon -c 100 -d 40 -p 10 (40s warm-up, 40s measure).
| Package | Requests/s | Latency (ms) | Throughput/MB |
|---|---|---|---|
| http | 167021 | 5.47 | 29.79 |
| fastify | 152567 | 6.02 | 27.35 |
| hono | 137181 | 6.81 | 22.50 |
| koa | 136575 | 6.79 | 24.36 |
| hapi | 118206 | 7.99 | 21.08 |
| express | 104533 | 9.06 | 18.64 |
| routup | 95979 | 9.93 | 18.67 |
Routup currently trails on raw req/s for the trivial GET / case — the focus of v5 has been runtime portability and ergonomics rather than micro-optimization. Throughput is competitive with express. To re-run the suite yourself, see the benchmarks repository.
Before starting to work on a pull request, it is important to review the guidelines for contributing and the code of conduct. These guidelines will help to ensure that contributions are made effectively and are accepted.
Made with 💚
Published under MIT License.
