Express.js ergonomics,
built for the next decade.
A typed HTTP framework for Node 20+ and Bun 1.1+. The shortest path from a working Express.js app to a modern, typed server - same shape, typed ctx, current-decade router.
Show me the code.
A full server. No res.send. No body-parser. Return a value and Ingenium reflects it to the wire.
import { ingenium } from 'ingenium'
const app = ingenium()
app.use(async (ctx, next) => {
const start = Date.now()
await next()
console.log(`${ctx.method} ${ctx.path} → ${Date.now()-start}ms`)
})
// Return a value → reflected to the wire
app.get('/', () => 'hello')
app.get('/users/:id', (ctx) => ({ id: ctx.params.id }))
app.post('/echo', async (ctx) => await ctx.body.json())
const server = await app.listen(3000)
console.log(`listening on :${server.port}`)$ curl localhost:3000/
hello
$ curl localhost:3000/users/42
{"id":"42"}
$ curl -XPOST localhost:3000/echo \
-H 'content-type: application/json' \
-d '{"hello":"world"}'
{"hello":"world"}
# server stdout:
GET / → 1ms
GET /users/42 → 0ms
POST /echo → 2msFix Express's three structural problems - without a new mental model.
Linear routing, untyped req/res, and per-request allocation. Same shape, different engine.
| Pain point | Express | Hono / Fastify | Ingenium |
|---|---|---|---|
| Router speed at 1000 routes | O(n) linear scan | O(k) trie | O(k) radix trie + wildcard backtrack |
| req / res types | any in practice | strict, but unfamiliar surface | strict, Express-shaped |
| Per-request allocation | new req/res/next each request | varies | pooled IngeniumContext, lazy getters |
| Middleware composition | re-walked per request | compose-on-register | lazy compose with dirty-bit recompose |
| Body parsing | body-parser always runs | always-on parsing | lazy via ctx.body.json() |
| Default body size limit | 100 KB | varies | 100 KB (matches Express) |
| Bun support | community shim | varies | first-class adapter |
| Migration cost from Express | n/a | high | low |
The pitch in one sentence: the shortest path from a working Express app to throughput competitive with Hono and Fastify.
Express →Ingenium
Most of your code stays put. Handlers can return values, body parsing is lazy, per-request state lives on ctx.state. That's the list.
import express from 'express'
const app = express()
app.use(express.json())
app.use((req, res, next) => {
req.startedAt = Date.now()
next()
})
app.get('/users/:id', (req, res) => {
res.json({ id: req.params.id })
})
app.post('/users', (req, res) => {
const body = req.body
res.status(201).json(user)
})
const router = express.Router()
router.get('/health',
(req, res) => res.json({ ok: 1 }))
app.use('/api', router)
app.use((err, req, res, next) => {
res.status(500).json({ err: err.message })
})
app.listen(3000)import { ingenium } from 'ingenium'
const app = ingenium()
app.use(ingenium.json()) // (no-op, parsing is lazy)
app.use(async (ctx, next) => {
ctx.state.startedAt = Date.now()
await next()
})
app.get('/users/:id', (ctx) =>
({ id: ctx.params.id }))
app.post('/users', async (ctx) => {
const body = await ctx.body.json()
return ctx.json(user, 201)
})
const router = ingenium.Router()
router.get('/health',
() => ({ ok: 1 }))
app.use('/api', router)
app.onError((err, ctx) => {
ctx.json({ err: err.message }, 500)
})
await app.listen(3000)Primitives an API team actually needs.
All opt-in. Native, not glued on. Each one is here because it fixes a real production incident pattern.
Timeout ceiling
Per-request timeout with IngeniumTimeoutError (503). A handler that never resolves no longer leaks the context, socket, and pool slot.
Hard body cap
maxRequestBytes enforced at the transport layer - before any consumer touches a byte. Works even when handlers stream the body.
Header injection guard
ctx.set(name, value) rejects CRLF immediately at the call site, not deep inside Node's wire path. Catches injection at the source.
JWT + JWKS
Asymmetric JWT (RS/PS/ES + JWKS) for Auth0, Okta, Cognito, Clerk, Supabase. Algorithm-confusion blocked, 'none' rejected unconditionally.
Late-write protection
_epoch counter on IngeniumContext detects orphaned-handler writes after a timeout. Stops cross-request response corruption on pool recycle.
Idempotency
ingenium.idempotency() with pluggable store. Default skips caching 5xx - transient errors don't get replayed for the entire TTL.
CSRF protection
Double-submit cookie (default) or synchronizer pattern with HMAC-signed tokens. Timing-safe verification, secret rotation supported.
Sessions
HMAC-SHA256-signed cookies, 144-bit ids, regenerate() for fixation defense, pluggable SessionStore - Redis-ready interface.
One app, any wire.
Pluggable transport adapters. Write once, swap the engine.
Ship your next API on Ingenium.
Same Express mental model. Current-decade router. Typed ctx. Production-grade primitives opt-in. Ten minutes from npm install to shipping.
MIT licensed · alpha · made for Node 20+ and Bun 1.1+