HTTP Runtimes
KickJS runs on Express by default, but the HTTP engine is a bootstrap-time choice. Controllers, modules, context decorators, DI, and the dev loop don't change when you swap engines — the runtime is an infrastructure decision, not an application rewrite.
import { bootstrap } from '@forinda/kickjs'
// Express — the zero-config default. Nothing to install or configure.
export const app = await bootstrap({ modules })import { bootstrap } from '@forinda/kickjs'
import { fastifyRuntime } from '@forinda/kickjs/fastify'
// Opt in to Fastify — same modules, same controllers.
export const app = await bootstrap({ modules, runtime: fastifyRuntime() })Why it works
Your controllers already speak RequestContext, not Express:
@Controller()
class UsersController {
@Get('/:id')
get(ctx: RequestContext) {
ctx.json({ id: ctx.params.id }) // engine-agnostic — works on any runtime
}
}KickJS turns decorators into an engine-neutral route table, and each runtime materializes that table onto its own router (real Express routes, real Fastify routes). ctx.json / ctx.html / ctx.sse / ctx.problem write through a small response driver, so the same handler code runs unchanged on every engine.
Fastify
Fastify ships as a subpath of the core package — there's no separate npm package. Install the engine peers alongside @forinda/kickjs:
pnpm add fastify @fastify/middieimport { fastifyRuntime } from '@forinda/kickjs/fastify'
export const app = await bootstrap({ modules, runtime: fastifyRuntime() })What works on Fastify today: routing, JSON / HTML / ctx.problem responses, connect-style middleware (the built-ins — helmet, cors, requestId, requestLogger, … — plus your own, bridged via @fastify/middie), request-scoped DI and context decorators (ctx.set / ctx.get), X-Request-Id propagation, error / 404 handling, Server-Sent Events (ctx.sse), and file uploads (@FileUpload → ctx.file / ctx.files, via @fastify/multipart).
Fastify's built-in pino logger is disabled (logger: false) so the kickjs requestLogger stays the single log format across engines.
h3
h3 is the HTTP layer behind Nitro / Nuxt. It ships as a subpath too:
pnpm add h3import { h3Runtime } from '@forinda/kickjs/h3'
export const app = await bootstrap({ modules, runtime: h3Runtime() })The binding targets h3 v1 (the stable, node-based surface). h3 v2's web-standard Request / Response core is the eventual target via a future web-standard driver — see the design spec §8.
Same surface as Fastify: routing, JSON / HTML, connect middleware (via h3's fromNodeMiddleware), context decorators, errors / 404, SSE, body validation, native body parsing, and file uploads (@FileUpload → ctx.file / ctx.files, via h3's built-in readMultipartFormData — no driver to install).
Capability matrix
Some ctx features depend on the engine. Calling an unsupported one raises a clear error rather than failing silently.
| Capability | Express | Fastify | h3 (v1) |
|---|---|---|---|
Routing + ctx.json | ✅ | ✅ | ✅ |
| Connect middleware | ✅ | ✅ (via middie) | ✅ (fromNodeMiddleware) |
| Context decorators | ✅ | ✅ | ✅ |
| Errors / 404 | ✅ | ✅ | ✅ |
| Server-Sent Events | ✅ | ✅ | ✅ |
| Validation | ✅ | ✅ | ✅ |
ctx.render (views) | ✅ | ❌ (no view engine) | ❌ (no view engine) |
File uploads (ctx.file) | ✅ (multer) | ✅ (@fastify/multipart) | ✅ (native multipart) |
The engine-native escape hatch
For genuinely engine-specific needs, the raw app and request/response are still reachable:
AdapterContext.app/app.getRuntimeApp()— the engine-native app instance.ctx.req/ctx.res— the engine-native request / response.
Under the default Express runtime these are typed as Express's Application, Request, and Response. The types follow the active runtime via the ActiveRuntime registry: set runtime: 'fastify' (or 'h3') in kick.config.ts and the kick/runtime typegen emits a KickRuntimeRegister augmentation that retypes them to that engine — Fastify's FastifyInstance / FastifyRequest / FastifyReply, or h3's App / H3Event.
Writing a custom runtime
A runtime implements the HttpRuntime contract (createApp, nodeHandler, mountRoutes, useConnect, serveStatic, setNotFound, setErrorHandler, capabilities). expressRuntime() is the reference implementation; the Fastify runtime is ~250 lines over the same contract. See the design spec for the full contract and rationale.