A production-grade, decorator-driven Node.js framework built on Express 5 and TypeScript.
NestJS ergonomics without the complexity — decorators, DI, module system, code generators, and end-to-end type safety, powered by Zod and Vite.
pnpm add @forinda/kickjs express reflect-metadata zod
pnpm add -D @forinda/kickjs-cliOr scaffold a new project:
npx @forinda/kickjs-cli new my-api
cd my-api && pnpm devA fresh kick new my-api scaffolds a complete project. Here are the files that matter, exactly as the CLI generates them:
// src/modules/hello/hello.service.ts
import { Service } from '@forinda/kickjs'
@Service()
export class HelloService {
greet(name: string) {
return { message: `Hello ${name} from KickJS!`, timestamp: new Date().toISOString() }
}
healthCheck() {
return { status: 'ok', uptime: process.uptime() }
}
}// src/modules/hello/hello.controller.ts
import { Controller, Get, Autowired, type Ctx } from '@forinda/kickjs'
import { HelloService } from './hello.service'
@Controller()
export class HelloController {
@Autowired() private readonly helloService!: HelloService
@Get('/')
index(ctx: Ctx<KickRoutes.HelloController['index']>) {
ctx.json(this.helloService.greet('World'))
}
@Get('/health')
health(ctx: Ctx<KickRoutes.HelloController['health']>) {
ctx.json(this.helloService.healthCheck())
}
}// src/modules/hello/hello.module.ts
import { defineModule } from '@forinda/kickjs'
import { HelloController } from './hello.controller'
export const HelloModule = defineModule({
name: 'HelloModule',
build: () => ({
routes() {
return { path: '/hello', controller: HelloController }
},
}),
})// src/modules/index.ts
import { defineModules } from '@forinda/kickjs'
import { HelloModule } from './hello/hello.module'
export const modules = defineModules().mount(HelloModule())// src/index.ts
import 'reflect-metadata'
import './config' // registers env schema before bootstrap
import { bootstrap } from '@forinda/kickjs'
import { modules } from './modules'
export const app = await bootstrap({ modules })
KickRoutes.HelloController['index']is generated bykick typegen(auto-runs onkick dev), giving fully typedctx.params,ctx.body, andctx.query. Env keys typed viaKickEnvafter running typegen too.
Factory-first extensibility — the full extension surface. No class hierarchies to inherit from.
defineAdapter() defineModule()
definePlugin() defineHttpContextDecorator()Custom DI container — constructor and property injection, three scopes (singleton / transient / request), zero external dependency. Slash-delimited tokens carry intent and scope from the type definition straight through to error messages.
const REPO = createToken<UserRepo>('app/users/repository')
container.register(REPO, PrismaUserRepo)Typed Context Contributors — defineHttpContextDecorator() populates ctx.set('key', value) once per request. dependsOn is typed against keyof ContextMeta so typos are TS errors, not boot-time MissingContributorError. Same registration runs across HTTP / WS / queue / cron.
End-to-end type safety via typegen — kick typegen (auto on kick dev) emits augmentations from source scan. ctx.params/body/query, @Inject literals, and asset paths all narrow as you save.
KickRoutes // ctx.params / body / query per route
KickJsPluginRegistry // @Inject literals
KickAssets // typed asset paths
KickEnv // ConfigService.get keysDecorator-driven:
@Controller @Get @Post @Put @Delete @Patch
@Service @Autowired @Middleware
@Cacheable @Cron @AssetDDD generators — full hook surface emitted so you delete what you don't need.
kick g module users # complete module scaffold
kick g adapter # full hook surface
kick g plugin # full plugin shapekick g agents — regenerates CLAUDE.md at the project root and .agents/AGENTS.md / .agents/GEMINI.md / .agents/COPILOT.md + per-skill .agents/skills/<slug>/SKILL.md. One CLI command keeps every AI coding agent in sync with the latest framework conventions.
Cooperative shutdown — observability SDKs (OpenTelemetry, Sentry) own SIGTERM without racing the framework. Promise.allSettled for adapter shutdown so one slow flush can't block siblings.
bootstrap({ processHooks: 'errors-only' })Zod-native validation — schemas double as OpenAPI documentation.
Vite HMR — single-port dev server, zero-downtime hot reload, preserves DB/Redis/Socket connections, customizable HMR log.
Auto OpenAPI — Swagger UI and ReDoc from decorators + Zod schemas; pluggable schema parser + UI renderer for adopters who want corporate branding.
Built-in middleware — helmet, CORS, CSRF, rate limiting, file uploads, request logging, request scope (AsyncLocalStorage).
DevTools dashboard — /_debug browser panel with topology, container, routes, metrics; adapter authors expose state via introspect() (type lives in @forinda/kickjs directly — no extra import needed) + devtoolsTabs() from @forinda/kickjs-devtools-kit.
Extensible CLI — custom commands in kick.config.ts, plugin generators registered as real Commander subcommands so they appear in kick g --help, jiti-powered TS config loading, walk-up project-root resolution.
KickJS deliberately ships a small, stable core. The extension surface — defineAdapter(), definePlugin(), defineHttpContextDecorator(), plus getRequestValue and processHooks from @forinda/kickjs — is the same one the framework itself uses, so adopter-built integrations stay first-class: typed DI, lifecycle hooks, the contributor pipeline, and DevTools introspect() all work without any framework changes.
Three packages ship with every project — kick new always installs them, and kick add won't list them as optional. Together they're the framework runtime + the dev/build/scaffold loop:
| Package | Description |
|---|---|
@forinda/kickjs |
Core framework — DI, decorators, Express 5, routing, middleware, contributors, request store, processHooks |
@forinda/kickjs-vite |
Vite plugin — single-port HMR, typegen watcher, customizable HMR log |
@forinda/kickjs-cli |
Scaffolding, DDD generators, custom commands, kick g agents, jiti-powered TS config loading, walk-up project root |
Everything else — swagger, the db family, queue, ws, devtools, drizzle, prisma — installs on demand. The catalog moves over time, so the live list lives next to the CLI rather than this README:
kick add --list # current optional catalog
kick add swagger drizzle # install several at onceBrowse packages/ in this repo for the full source layout.
Runnable reference apps live in forinda/kickjs-examples-archive — Drizzle / Prisma / Mongoose / @forinda/kickjs-db task apps, multi-tenant patterns, and a minimal starter. Open the archive's README for the current catalog.
git clone https://github.com/forinda/kickjs-examples-archiveThe fastest way to start a real project is still kick new <name> — kick.config.ts, tsconfig, vite.config, modules, and env wiring scaffolded for you. The archive is for reading reference patterns; the CLI is for starting your own.
# Project lifecycle
kick new my-api # Scaffold project (rest | ddd | cqrs | minimal)
kick dev # Vite HMR dev server (~200ms reload)
kick build && kick start # Production build + run
# Code generation
kick g module users # Full DDD module
kick g module users --repo prisma # …with a Prisma repository
kick g module users --repo drizzle # …with a Drizzle repository
kick g scaffold post title:string body:text:optional # CRUD from field defs
kick g controller users # Single @Controller class
kick g service payment # Single @Service class
kick g adapter websocket # AppAdapter — every hook stubbed + JSDoc
kick g plugin analytics # KickPlugin — every hook stubbed + JSDoc
kick g job / dto / guard / middleware / test # one-file scaffolds
# AI agent docs (regenerate from upstream templates after framework upgrades)
kick g agents # CLAUDE.md (root) + .agents/{AGENTS,GEMINI,COPILOT}.md + .agents/skills/*/SKILL.md
kick g agents --only skills -f # Just the per-skill SKILL.md files
kick g agents --only gemini -f # Just .agents/GEMINI.md
# Package management
kick add swagger drizzle # Install KickJS packages with peer deps
kick add --list # Show all available packages
# Introspection
kick info # System + framework version
kick inspect # Inspect a running KickJS app
kick tinker # Interactive REPL with full DI graph| Area | Choice | Why |
|---|---|---|
| Runtime | Node.js 20+ | LTS with native ESM |
| HTTP | Express 5 | Mature, async middleware, wide ecosystem |
| Validation | Zod | Runtime + static types, doubles as OpenAPI schema |
| Build | Vite 8 | Unified toolchain — library builds, HMR, SSR |
| Test | Vitest 4 | ESM-native, fast, Vite-compatible |
| Logging | Pino | Fastest Node.js logger, structured JSON |
| Monorepo | pnpm + Turborepo | Efficient deps, build caching |
| Feature | Node 20+ | Node 22+ | Node 24+ | Bun | Deno |
|---|---|---|---|---|---|
| Production | Yes | Yes | Yes | Experimental | No |
| Dev Mode (HMR) | Yes | Yes | Yes | No | No |
| Tests (Vitest) | Yes | Yes | Yes | Partial | No |
CLI (kick) |
Yes | Yes | Yes | Experimental | No |
| Pure ESM Import | Yes | Yes | Yes | Yes | Yes |
Node 20 is the minimum supported version (LTS with native ESM).
Bun: core DI and decorators work; full HTTP pipeline is experimental.
Deno: blocked by
reflect-metadataandpinodependencies. UseLogger.setProvider()for core-only usage.
git clone https://github.com/forinda/kick-js.git
cd kick-js
pnpm install && pnpm build && pnpm testSee CONTRIBUTING.md for the full guide.
MIT — see LICENSE