A production-grade, decorator-driven Node.js framework built on Express 5 and TypeScript.
NestJS ergonomics without the complexity — decorators, DI, module system, and code generators, 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 { type AppModule, type ModuleRoutes, buildRoutes } from '@forinda/kickjs'
import { HelloController } from './hello.controller'
export class HelloModule implements AppModule {
routes(): ModuleRoutes {
return {
path: '/hello',
router: buildRoutes(HelloController),
controller: HelloController,
}
}
}// src/modules/index.ts
import type { AppModuleClass } from '@forinda/kickjs'
import { HelloModule } from './hello/hello.module'
export const modules: AppModuleClass[] = [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.
- Custom DI container — constructor and property injection, no external dependency
- Decorator-driven —
@Controller,@Get,@Post,@Service,@Autowired,@Middleware - Zod-native validation — schemas double as OpenAPI documentation
- Vite HMR — zero-downtime hot reload, preserves DB/Redis/Socket connections
- DDD generators —
kick g module usersscaffolds 18 files in 2 seconds - Auto OpenAPI — Swagger UI and ReDoc from decorators + Zod schemas
- Built-in middleware — helmet, CORS, CSRF, rate limiting, file uploads, request logging
- Typed adapters —
AdapterContextwithExpress,http.Server,env,isProduction - Pluggable — adapters for database, auth, cache, swagger, queues, WebSocket, cron
- Extensible CLI — custom commands in
kick.config.ts
| Package | Description |
|---|---|
@forinda/kickjs |
Core framework — DI, decorators, Express 5, middleware, routing |
@forinda/kickjs-config |
Zod-based env validation, ConfigService, @Value |
@forinda/kickjs-swagger |
Auto OpenAPI from decorators + Zod |
@forinda/kickjs-cli |
Scaffolding, DDD generators, custom commands |
@forinda/kickjs-testing |
createTestApp, createTestModule |
@forinda/kickjs-prisma |
Prisma adapter (v5/6/7) |
@forinda/kickjs-drizzle |
Drizzle adapter, query builder |
@forinda/kickjs-auth |
JWT, API key, OAuth strategies |
@forinda/kickjs-ws |
WebSocket with @WsController |
@forinda/kickjs-queue |
BullMQ, RabbitMQ, Kafka |
@forinda/kickjs-cron |
@Cron decorator scheduling |
@forinda/kickjs-mailer |
SMTP, Resend, SES |
@forinda/kickjs-graphql |
@Resolver, @Query, @Mutation |
@forinda/kickjs-otel |
OpenTelemetry tracing + metrics |
@forinda/kickjs-devtools |
Debug dashboard at /_debug |
@forinda/kickjs-notifications |
Email, Slack, Discord, webhook |
@forinda/kickjs-multi-tenant |
Tenant resolution middleware |
@forinda/kickjs-ai |
Providers (OpenAI, Anthropic), @AiTool, memory, RAG, agent loop |
@forinda/kickjs-mcp |
Model Context Protocol server adapter + kick mcp CLI |
| Example | What it shows |
|---|---|
| minimal-api | Simplest possible app — bootstrap + one controller |
| v2-showcase-api | Full v2 feature tour: typed Ctx<KickRoutes>, KickEnv, createToken, DDD modules |
| v3-preview | Preview of upcoming v3 APIs |
| jira-drizzle-api | Full Jira clone — PostgreSQL + Drizzle, 14 DDD modules |
| jira-prisma-api | Full Jira clone — PostgreSQL + Prisma 6 |
| jira-prisma-v7-api | Full Jira clone — PostgreSQL + Prisma 7 (driver adapters) |
| jira-mongoose-api | Full Jira clone — MongoDB + Mongoose |
| graphql-api | GraphQL with @Resolver, @Query, @Mutation |
| devtools-api | DevTools dashboard + reactive state |
| microservice-api | OTel + DevTools + Swagger template |
| otel-api | OpenTelemetry console tracing |
| joi-api | Custom Joi SchemaParser for Swagger + Joi validation middleware |
-
Build the CLI first (if not already built):
pnpm build
-
Scaffold the example using the local CLI from the
examples/directory (pass all flags to avoid interactive prompts):cd examples node ../packages/cli/bin.js new my-example-api \ --template minimal --pm pnpm --repo inmemory --no-git --no-install --forceAvailable flags:
--template <type>—rest | graphql | ddd | cqrs | minimal--pm <manager>—pnpm | npm | yarn--repo <type>—prisma | drizzle | inmemory | custom--no-git— skip git init (use repo root's git)--no-install— skip install (runpnpm installfrom root instead)--force— overwrite existing directory without prompting
This generates
package.json,tsconfig.json,vite.config.ts,kick.config.ts,src/index.ts, and all boilerplate. -
The workspace already includes
examples/*inpnpm-workspace.yaml— no changes needed there. -
Update the generated
package.json:- Rename to
@forinda/kickjs-example-<name> - Set
"private": true - Replace published
@forinda/kickjs*deps withworkspace:*references
- Rename to
-
Add a row to the Example Apps table in this README.
-
Run from the repo root to link workspace deps and verify:
pnpm install && pnpm build
kick new my-api # Scaffold project
kick dev # Vite HMR dev server (~200ms reload)
kick build # Production build
kick start # Run production
kick g module users # Generate DDD module (18 files)
kick g module users --repo prisma # With Prisma repository
kick g module users --repo drizzle # With Drizzle repository| 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).
Node 24: mailer tests need
server.deps.externalfor nodemailer CJS.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