Generators
The kick generate command (alias kick g) scaffolds code following KickJS conventions. Generators produce files with proper imports, decorators, and DI registration.
Run kick g --list to see all available generators at a glance.
Default paths are conventions, not rules
Generators emit files into src/modules/, src/middleware/, src/plugins/, src/adapters/, and friends because that's the layout kick new ships. None of these paths are hard-coded in the framework — adopters can override modules.dir in kick.config.ts, pass -o, --out per invocation, or rearrange entirely. The trees below show the default layout that the generators produce out of the box.
Plugins can ship their own generators
Third-party packages can extend kick g via the CLI plugin contract. Generators authored with defineGenerator and exposed through a KickCliPlugin.generators[] show up in kick g --list for any project that wires the plugin. See Plugin Generators for the authoring path.
kick g module
Generate one or more modules. Structure depends on the project pattern in kick.config.ts.
kick g module product
kick g module user task project # generate multiple at oncePattern determines structure
The pattern field in kick.config.ts controls what files are generated. You can also override per-invocation with --pattern.
Pattern: rest (default — recommended for most apps)
Generates a flat, simple module with a controller that delegates to a service. Every endpoint works out of the box with the in-memory repository.
pnpm exec kick g module product --pattern restproducts/
product.module.ts # Module declaration (register + routes)
product.constants.ts # Query config (filterable, sortable, searchable)
product.controller.ts # @Controller with full CRUD
product.service.ts # @Service wrapping the repository
product.repository.ts # Factory + contract (ReturnType) + DI token, one file
dtos/
create-product.dto.ts # Zod schema for POST
update-product.dto.ts # Zod schema for PUT
product-response.dto.ts # Response interface
__tests__/
product.controller.test.ts
product.repository.test.tsThe controller injects ProductService, which handles all CRUD. No use-cases, no domain layer — just clean REST. A custom repo name (e.g. --repo postgres) does not change the file name or the identifiers — you get the same product.repository.ts with an unimplemented body and TODO markers naming the store.
Pattern: minimal
Generates only a module declaration and a bare controller. Use this as a starting point when you want full control.
pnpm exec kick g module product --pattern minimalproducts/
product.module.ts # Module declaration
product.controller.ts # Bare @Controller with a single GET endpointModule Flags
| Flag | Description | Default |
|---|---|---|
--pattern <type> | Override project pattern: rest, minimal | from config or rest |
--no-tests | Skip test file generation | false |
--repo <name> | Repository name (see Repository Variants) | from config or inmemory |
--no-pluralize | Use singular names for folders and routes | from config or false |
--minimal | Shorthand for --pattern minimal | false |
--modules-dir <dir> | Modules directory | from config or src/modules |
-f, --force | Overwrite existing files without prompting | false |
Pluralization
By default, module names are pluralized: kick g module user creates src/modules/users/ with route /users.
Disable pluralization per-command or globally:
# Per-command
kick g module user --no-pluralize # → src/modules/user/, route /user
# Via config (applies to all generators)
export default defineConfig({
pluralize: false,
})The --no-pluralize flag always wins over the config value.
Config-Aware Defaults
The generator reads kick.config.ts for defaults. Module-specific settings live under the modules key:
// kick.config.ts
export default defineConfig({
pattern: 'rest',
modules: {
dir: 'src/modules',
// deprecated — the name no longer changes the generated code, only
// the TODO text in the stub body
repo: { name: 'postgres' },
pluralize: true,
schemaDir: 'src/db/schema',
style: 'define', // 'define' (default) or 'class' — see below
},
})Module declaration style
The modules.style field controls what kick g module and kick g scaffold emit for the module declaration:
'define'(default) —defineModule({ name, build: () => ({...}) })factory form. MirrorsdefineAdapter/definePlugin/defineContextDecorator. The orchestrator inserts the factory-call form (TaskModule()) intosrc/modules/index.ts.'class'— legacyclass FooModule implements AppModule { ... }form. The orchestrator inserts the bare class reference (TaskModule) into the modules array.
The framework runtime accepts both shapes regardless of this setting — the flag controls codegen output only. kick rm module matches both forms, so flipping the flag mid-project doesn't break un-registration.
# Pin a project to class form (existing-codebase consistency, etc.)
# kick.config.ts → modules: { style: 'class' }
kick g module task
# → src/modules/tasks/task.module.ts emits:
# export class TaskModule implements AppModule { register() {...} routes() {...} }
# → src/modules/index.ts: [TaskModule]kick g module user # uses rest pattern + postgres repo (from config)
kick g module user --pattern minimal # overrides to minimal structure
kick g module user --repo mongo # overrides repo, keeps rest pattern
kick g module user --no-pluralize # overrides pluralizationBackward compatibility
Top-level modulesDir, defaultRepo, pluralize, and schemaDir are still supported but deprecated. Prefer the modules block.
Overwrite Protection
If you run the generator for a module that already exists, the CLI prompts for each file:
File already exists: index.ts
Overwrite? (y/n/a = yes/no/all)- y — overwrite this file
- n — skip this file
- a — overwrite all remaining files without prompting
Use --force to skip all prompts and overwrite everything.
Repository Variants
The --repo flag is name-based. There is exactly one built-in repository — inmemory — and any other name scaffolds a generic custom-repository stub.
| Value | Generated file | Body |
|---|---|---|
unset / inmemory | {name}.repository.ts | Working, zero-dependency Map-based store. The default. |
| any name | {name}.repository.ts | Unimplemented, with TODO markers naming the store you asked for. |
The file name and identifiers are the same either way. Only the body differs:
kick g module user # → user.repository.ts, working Map body
kick g module user --repo postgres # → user.repository.ts, "write the postgres query"The generator used to bake the store into the names — postgres-user.repository.ts exporting PostgresUserRepository, whose every method read and wrote a Map. An app could be booted and manually tested against a class asserting Postgres while every write went to a store that empties on restart. The name was the lie, not the Map, so the name is gone.
You can set the default via kick.config.ts. The CLI suggests the { name } object form for any non-inmemory repo:
export default defineConfig({
modules: {
// Built-in — working in-memory code
repo: 'inmemory',
// Custom — generates stub with TODO markers (preferred object form)
// deprecated — the name no longer changes the generated code, only
// the TODO text in the stub body
repo: { name: 'postgres' },
},
})Wiring a real database
The only built-in repository is inmemory. For persistence, scaffold a generic custom repository (any name, e.g. postgres) and wire it to your own client, or install the first-party DB layer with kick add db (@forinda/kickjs-db, whose /pg, /sqlite and /mysql subpaths carry the dialects) and implement the generated stub against it.
Auto-Registration
When you generate a module, the generator automatically updates src/modules/index.ts. defineModule factories are called at the registration site — the generator emits Module() so bootstrap receives the module instance. If the file does not exist, it creates one:
import type { AppModuleEntry } from '@forinda/kickjs'
import { ProductModule } from './products'
export const modules: AppModuleEntry[] = [ProductModule()]If index.ts already exists, it appends the import and adds the module factory call to the array:
import type { AppModuleEntry } from '@forinda/kickjs'
import { UserModule } from './users'
import { ProductModule } from './products'
export const modules: AppModuleEntry[] = [UserModule(), ProductModule()]Generated Module Declaration
The module file (product.module.ts) registers the repository binding in the DI container and declares routes. With the default 'define' style:
import { defineModule } from '@forinda/kickjs'
import { PRODUCT_REPOSITORY, createProductRepository } from './product.repository'
import { ProductController } from './product.controller'
export const ProductModule = defineModule({
name: 'ProductModule',
build: () => ({
register(container) {
container.registerFactory(PRODUCT_REPOSITORY, () => createProductRepository())
},
routes() {
return {
path: '/products',
controller: ProductController, // framework derives the router via buildRoutes()
}
},
}),
})kick g scaffold
Generate a full CRUD module from field definitions. Unlike kick g module, which creates empty DTOs, scaffold generates Zod schemas with concrete fields and a working repository — ready to use immediately. It produces the same flat REST layout as kick g module (controller + service + field-aware DTOs + repository), not a layered DDD structure.
pnpm exec kick g scaffold Post title:string body:text:optional published:boolean:optionalField Syntax
Each field uses the format name:type or name:type:optional:
| Type | TypeScript | Zod | Example |
|---|---|---|---|
string | string | z.string() | title:string |
text | string | z.string() | body:text |
number | number | z.number() | price:number |
int | number | z.number().int() | age:int |
float | number | z.number() | rating:float |
boolean | boolean | z.boolean() | active:boolean |
date | string | z.string().datetime() | createdAt:date |
email | string | z.string().email() | email:email |
url | string | z.string().url() | website:url |
uuid | string | z.string().uuid() | externalId:uuid |
json | any | z.any() | metadata:json |
enum:a,b,c | 'a' | 'b' | 'c' | z.enum(['a','b','c']) | status:enum:draft,published |
Optional Fields
Append :optional to make a field optional (shell-safe, no quoting needed):
pnpm exec kick g scaffold Post title:string body:text:optional published:boolean:optionalThe ? syntax also works but requires quoting in bash/zsh because ? is a shell glob character:
# These need quotes — ? triggers shell glob expansion without them
kick g scaffold Post title:string "body:text?" "published:boolean?"
kick g scaffold Post title:string "body?:text" "published?:boolean"Shell glob expansion
body:text? without quotes is interpreted by bash/zsh as a file glob pattern — the ? matches any single character. Always use :optional for unquoted usage, or wrap the field in quotes.
Generated Structure
Scaffold emits the same flat REST module layout as kick g module, but with field-aware DTOs and a working repository instead of empty stubs. Inside posts/:
| File | Description |
|---|---|
post.module.ts | Module declaration (register + routes) |
post.constants.ts | Query config (filterable, sortable, searchable) + token |
post.controller.ts | Full CRUD with typed Ctx |
post.service.ts | @Service wrapping the repository |
post.repository.ts | Factory + derived contract + DI token, one file |
dtos/create-post.dto.ts | Zod schema built from the fields |
dtos/update-post.dto.ts | All fields optional |
dtos/post-response.dto.ts | Response interface |
__tests__/ | Controller + repository tests |
post.repository.ts is always the Map-backed factory — kick g scaffold takes no --repo flag, so it can't name a store in the generated prose the way kick g module --repo postgres does. Replace the factory body with your own queries; the contract is ReturnType<typeof createPostRepository>, so nothing else changes.
Scaffold Flags
| Flag | Description | Default |
|---|---|---|
--no-entity | Skip entity + value object generation | false |
--no-tests | Skip test file generation | false |
--no-pluralize | Use singular names | from config or false |
--modules-dir <dir> | Modules directory | from config or src/modules |
Example
pnpm exec kick g scaffold User name:string email:email:optional age:int role:enum:admin,user,guestGenerates DTOs like:
// create-user.dto.ts
import { z } from 'zod'
export const createUserSchema = z.object({
name: z.string(),
email: z.string().email().optional(),
age: z.number().int(),
role: z.enum(['admin', 'user', 'guest']),
})
export type CreateUserDTO = z.infer<typeof createUserSchema>kick rm module
Remove one or more modules. Deletes the module directory and unregisters it from src/modules/index.ts.
kick rm module user # remove a single module
kick rm module user task project # remove multiple at once
kick rm module user --force # skip confirmation promptThe command also supports kick remove module as the full form.
kick g agents
Regenerate the AI-agent documentation from the latest CLI templates. Everything lands under .agents/ except CLAUDE.md, which stays at the project root because Claude Code auto-loads it from there. Use this after a KickJS upgrade to pull in new conventions, decorator changes, and gotchas without manually copy-pasting between projects.
kick g agents # Refresh everything (prompts before overwrite)
kick g agents -f # Refresh everything, no prompt
kick g agents -f --only skills # Just .agents/skills/<slug>/SKILL.md
kick g agents -f --only claude # Just CLAUDE.md
kick g agents -f --only agents # Just .agents/AGENTS.md
kick g agents -f --only gemini # Just .agents/GEMINI.md
kick g agents -f --only copilot # Just .agents/COPILOT.md
kick g agents -f --only both # .agents/AGENTS.md + CLAUDE.md (skip skills)Aliases: kick g agent-docs, kick g ai-docs.
The generator auto-detects:
- Project name from
package.jsonname(strips@scope/prefix). - Package manager from
package.jsonpackageManager(corepack convention). - Template from
kick.config.tspatternfield (defaults torest).
Override any of those with --name, --pm, --template.
| Flag | Description | Default |
|---|---|---|
--only <which> | agents | claude | skills | gemini | copilot | both | all | all |
--name <name> | Project name (overrides package.json) | auto |
--pm <pm> | Package manager (overrides package.json) | auto |
--template <template> | rest | minimal | from kick.config.ts |
-f, --force | Overwrite without prompting | false |
Local customisations
Generated files are overwritten on regeneration. Keep project-specific notes in .agents/AGENTS.local.md, .agents/GEMINI.local.md, or a per-skill .agents/skills/<slug>/SKILL.local.md — .local.md siblings are never overwritten.
What's in each file
.agents/AGENTS.md— narrative reference: project structure, conventions, decorator patterns, env wiring, common pitfalls. Read first by every AI agent.CLAUDE.md(project root) — thin redirect to.agents/AGENTS.mdplus Claude-specific affordances (slash commands, persistent memory,/loop,/schedule)..agents/GEMINI.md,.agents/COPILOT.md— the same context shaped for Gemini and Copilot..agents/skills/<slug>/SKILL.md— one folder per skill, each a short rigid recipe keyed to a trigger (add-module,add-adapter,write-controller-test,bootstrap-export,thin-entry-file,context-contributor,env-wiring-check,refresh-agent-docs,deny-list). Agents that auto-discover skills pick each up from its frontmatter — no top-level index file.
Module-Scoped vs Global Generation
When to use kick g module vs standalone generators
kick g module <name> creates a full flat REST module in one shot — controller, service, DTOs, repository (factory + contract + token in one file), and tests. Use this when starting a new feature.
Standalone generators (kick g controller, kick g service, kick g dto, kick g guard, kick g middleware) create a single file. Use these to add files to an existing module or to create app-level artifacts that don't belong to any module.
The --module flag
Standalone generators support -m, --module <name> to place the file inside an existing module's folder structure:
# Module-scoped — file goes into the module's flat structure
kick g controller auth -m users # → src/modules/users/auth.controller.ts
kick g service payment -m orders # → src/modules/orders/payment.service.ts
kick g dto create-user -m users # → src/modules/users/dtos/create-user.dto.ts
kick g guard admin -m users # → src/modules/users/guards/admin.guard.ts
kick g middleware cache -m products # → src/modules/products/middleware/cache.middleware.ts
# Global / app-level — file goes to the standalone default directory
kick g controller health # → src/controllers/health.controller.ts
kick g middleware logger # → src/middleware/logger.middleware.ts
kick g guard rate-limit # → src/guards/rate-limit.guard.tsThe --module flag respects modules.dir from kick.config.ts. If you also pass -o, --out <dir>, the explicit output directory always wins.
Adapters are always app-level
Adapters (kick g adapter) do not support --module because they configure app-wide lifecycle hooks and are not scoped to a single module.
Folder mapping
When --module is used, each artifact type maps to a folder inside the module's flat layout:
| Generator | Module folder |
|---|---|
| controller | module root |
| service | module root |
| dto | dtos/ |
| guard | guards/ |
| middleware | middleware/ |
Standalone Generators
Each generator creates a single file. Use -m <module> to scope it to a module, or -o <dir> for a custom directory.
kick g controller
kick g controller auth
kick g controller auth -o src/modules/authGenerates a @Controller() class with basic @Get('/') route. Default output: src/controllers/.
kick g service
pnpm exec kick g service paymentGenerates a @Service() class. Default output: src/services/.
kick g middleware
pnpm exec kick g middleware loggerGenerates a connect-style middleware function (runs on Express, Fastify and h3 through the runtime seam). Default output: src/middleware/.
kick g guard
pnpm exec kick g guard adminGenerates a route guard function. Default output: src/guards/.
kick g adapter
pnpm exec kick g adapter websocketGenerates a defineAdapter() factory with all lifecycle hooks stubbed out. Default output: src/adapters/.
kick g plugin
kick g plugin analytics # → src/plugins/analytics.plugin.ts
kick g plugin feature-flags -o ./src/plugins # explicit output dirGenerates a KickPlugin factory function with every optional hook (register, modules, adapters, middleware, onReady, shutdown) stubbed out and commented, plus an options interface. Default output: src/plugins/.
The generated factory is camelCased from the plugin name — kick g plugin feature-flags emits featureFlagsPlugin so it can be imported and called inline at bootstrap time:
import { bootstrap } from '@forinda/kickjs'
import { featureFlagsPlugin } from './plugins/feature-flags.plugin'
export const app = await bootstrap({
modules,
plugins: [featureFlagsPlugin({ enabled: true })],
})Plugins are the canonical place to wire DI bindings, contribute modules or adapters, and register middleware without writing a full adapter. See the plugins guide for the full lifecycle and patterns.
kick g dto
pnpm exec kick g dto create-userGenerates a schema with its inferred TypeScript type. Default output: src/dtos/.
The schema is written against whichever validation library the project depends on — Zod, Valibot, or Yup — so a scaffold created with --schema valibot gets a Valibot schema rather than an import it cannot resolve. Same for the DTOs kick g module emits.
kick g test
kick g test user-service # → src/__tests__/user-service.test.ts
kick g test user-service -m users # → src/modules/users/__tests__/user-service.test.tsGenerates a Vitest test scaffold with Container.reset() setup. Default output: src/__tests__/.
kick g contributor
Generate a Context Contributor — the typed alternative to @Middleware() when the job is to compute a value other code reads off ctx.
kick g contributor tenant
kick g contributor tenant -m users # inside a module folder
kick g contributor tenant --params "source:string" # emits the withParams<T>() form
kick g contributor trace -t bare # ExecutionContext instead of RequestContext| Flag | Description |
|---|---|
-t, --type <type> | http (default, RequestContext) or bare (ExecutionContext) |
-k, --key <key> | Context key it writes. Defaults to the camelCase of the name |
--params <fields> | Per-call params, e.g. "source:string,region:number" — emits withParams<T>() |
-m, --module <module> | Place inside a module folder |
-o, --out <dir> | Output directory (overrides --module) |
Common Options
All standalone generators accept:
| Flag | Description | Default |
|---|---|---|
-o, --out <dir> | Output directory | Varies by type |
-m, --module <name> | Place inside a module folder | - |
Names are automatically converted: kick g module user-profile produces UserProfile (PascalCase) for classes and user-profile (kebab-case) for file names. Module names are pluralized for the directory (user-profiles/) unless --no-pluralize is passed or pluralize: false is set in config.