App and Router

The ingenium() factory returns an App - your top-level HTTP entry point. Apps and Routers share the same registration surface, and routers can mount routers, so you can compose your routes however you like.

Creating an app and mounting routers

import { ingenium, Router } from 'ingenium'

const app = ingenium({ poolSize: 1024, trustProxy: false })

// HTTP methods - same surface as Express
app.get('/', handler)
app.post('/users', handler)
app.put('/users/:id', handler)
app.patch('/users/:id', handler)
app.delete('/users/:id', handler)
app.head('/users/:id', handler)
app.options('/users/:id', handler)
app.method('OPTIONS', '/anywhere', handler)  // any method by string

// Mountable routers
const api = Router()
api.get('/health', () => ({ ok: true }))
api.use('/notes', notesRouter)               // routers can mount routers
app.use('/api', api)

// Middleware
app.use(globalMiddleware)
app.use('/admin', adminOnlyMiddleware)
app.use('/admin', adminRouter)               // mount middleware OR router

// Lifecycle
await app.compose()                          // pre-warm; runs lazily on first request otherwise
const server = await app.listen(3000, '0.0.0.0')
await server.close({ gracefulTimeoutMs: 10_000 })

Path syntax

:param (required), :param? (optional), *wildcard (greedy tail). Static segments win over params, params win over wildcards, but the matcher backtracks one level to a wildcard if the param branch dead-ends.

Composition timing

Registration is journaled, not eagerly composed. The trie and composed handlers are built on first request (or via app.compose()). Adding routes after listen() sets a dirty bit and triggers recompose on the next request - tests that register routes per-test work without ceremony.

Tip: Call await app.compose() before listen() if you want the first request to skip the compose step. Otherwise the lazy compose runs once on the first incoming request.

Where to next?

  • IngeniumContext - the typed ctx your handlers receive.
  • Middleware - the dispatch model and the built-ins.
  • Errors - error classes and the onError boundary.
  • Plugins - lifecycle hooks, decorators, and register.