Module Commands
Generate and manage modules — the building blocks of your application. Each module is a self-contained directory under src/modules/ with its own controllers, routes, schemas, services, helpers, middlewares, types, models, jobs, and seeders.
npm run maker <command> [options]pnpm maker <command> [options]yarn maker <command> [options]bun maker <command> [options]Naming
Every command takes a name in any casing you like. The CLI folds it to one canonical form and derives four shapes from it, so gameConfig, GameConfig, game_config and game-config all produce exactly the same files.
| Used for | Form | Example |
|---|---|---|
| File name and import path | kebab-case | game-config.ts |
| Table in the database | snake_case | game_configs |
| Variable in code | camelCase | gameConfigs |
| Class and schema names | PascalCase | GameConfigSeeder |
These forms were always the convention; from 4.2.0 the generators enforce them instead of writing whatever you typed.
What gets normalised
| You type | Canonical name |
|---|---|
game-config | game-config |
game_config | game-config |
game-SUITE, GAME-SUITE | game-suite |
gameSuite, GameSuite | game-suite |
game-config_2 | game-config-2 |
gamesuite, GAMESUITE | gamesuite |
gamE-SUITE-Engine | gam-e-suite-engine |
Two rules produce that last row. A capital inside a word marks a boundary, the same way it does in gameSuite — so the E in gamE splits it into gam and e, and the result keeps that capital in the PascalCase form (GamESuiteEngine). A word written without any separator carries no boundary at all, so gamesuite stays a single word rather than guessing where game ends.
Both readings are valid TypeScript and produce the same file every time, and any later casing of the same words resolves back to the module you already created:
bun maker module:make gamE-SUITE-Engine # creates src/modules/gam-e-suite-engine
bun maker module:make-model GAM-E-SUITE-ENGINE game-config # same moduleNormalisation only folds separators and case. A name that cannot become a safe identifier is still rejected, with a suggestion, e.g. game config → "Use "game-config" instead."
Table name pluralisation
The SQL table name is the plural of the model name. The rule is deliberately small: a consonant followed by y becomes ies, anything else takes a plain s.
| Model name | Table | Exported identifier |
|---|---|---|
spin | spins | spins |
audit-entry | audit_entries | auditEntries |
game-config | game_configs | gameConfigs |
ledger-entry | ledger_entries | ledgerEntries |
English irregulars (person → people, address → addresses) are not modelled. If your model needs one, rename the table and the export by hand after generating.
Upgrading from before 4.2.0
If you generated a multi-word name before 4.2.0, the files it produced do not compile — the name was written into identifier positions, so game-configService was read as subtraction. Re-run the generator with --force to regenerate them:
npm run maker module:make-model <module> game-config --force
npm run maker module:make-seeder <module> game-config --force--force only rewrites files; it never renames or deletes anything. A module folder that uses an older separator (for example game_config rather than game-config) is still found, so existing projects keep working without being renamed.
Scaffold Commands
module:make <name>
Create a complete module with all default scaffolding. Pass several names to create multiple modules in one run, or --path=<panel> to nest the module under a panel folder.
| Generated | Path |
|---|---|
| Facade | src/modules/<name>/facade.ts |
| Providers | src/modules/<name>/providers.ts |
| Controller | src/modules/<name>/controllers/<name>.ts |
| Schema | src/modules/<name>/schemas/<name>.ts |
| Service | src/modules/<name>/services/<name>.ts |
| Route | src/modules/<name>/routes/index.ts |
| Model | src/modules/<name>/database/models/<name>.ts |
| Seeder | src/modules/<name>/database/seeders/<name>.ts |
src/modules/posts/
├── controllers/
│ └── posts.ts
├── database/
│ ├── models/
│ │ └── posts.ts
│ └── seeders/
│ └── posts.ts
├── routes/
│ └── index.ts
├── schemas/
│ └── posts.ts
├── services/
│ └── posts.ts
├── facade.ts
└── providers.tsThe controller, schema, service and route adapt to the OPEN_API environment variable at scaffold time — see OpenAPI Mode.
The generated providers.ts is where the module plugs its own implementations into the application — it is imported automatically at boot. See Providers.
module:make-notification [name]
Notifications are opt-in: this command creates the notification backend module, then module:migrate creates its table. Default name is notification.
The command always writes the HTTP surface. It writes the store the first time — the table, model and provider are a singleton — and prints the migration command to run:
bun maker module:make-notification notification
bun maker module:migrate notification| File | Written | Purpose |
|---|---|---|
controllers/notification.ts | every run | 5 handlers: list, unreadCount, markRead, markAllRead, remove |
schemas/notification.ts | every run | Zod/OpenAPI schemas |
routes/index.ts | every run | 5 routes under authMiddleware |
database/models/notification.ts | first run | Polymorphic table, no auth foreign key |
providers/notification.ts | first run | Default Drizzle store, registers itself |
jobs/mail.ts | first run | Queue handler for notification:mail |
| Option | Default | Description |
|---|---|---|
--path=<panel> | — | Nest the module under a panel folder, e.g. --path=admin |
--notifiable=<type> | user | Owner type recorded by the generated controller |
--force | — | Overwrite the files of a module that already exists |
A second notification surface (for example a panel) reuses the existing store, so no second table is created and no migration is needed:
bun maker module:make-notification alerts --path=admin --notifiable=teamThe output states whether the store was included or reused:
Notification module ready: admin/alerts
Notification store reused: src/modules/notification/database/models/notification.ts
Panel: admin -> /api/admin/alerts
Notifiable type: teamTIP
UI integration is manual — see Notification Guide for Vue copy-paste components and setup instructions.
Component Commands
Add individual components to an existing module. All support --force (overwrite), --dry-run (preview) and --path=<panel> (address a module inside a panel folder).
module:make-route <module> [controller]
Generate a standalone route file for an existing module. Without a name it writes routes/index.ts; with a name it writes routes/<name>.ts. The stub ships with placeholder handlers — wire it to a controller and schemas when ready.
npm run maker module:make-route posts
npm run maker module:make-route posts custom-controllerpnpm maker module:make-route posts
pnpm maker module:make-route posts custom-controlleryarn maker module:make-route posts
yarn maker module:make-route posts custom-controllerbun maker module:make-route posts
bun maker module:make-route posts custom-controllermodule:make-controller <module> [name]
Generate a controller file at controllers/<name>.ts. If no name given, uses the module name. When a matching services/<name>.ts exists (or the legacy controllers/<name>.service.ts), the controller delegates to that service; otherwise it gets standalone handlers.
npm run maker module:make-controller posts
npm run maker module:make-controller posts adminpnpm maker module:make-controller posts
pnpm maker module:make-controller posts adminyarn maker module:make-controller posts
yarn maker module:make-controller posts adminbun maker module:make-controller posts
bun maker module:make-controller posts adminmodule:make-model <module> [name]
Generate a Drizzle model file with dialect-aware schema (MySQL, Postgres, or SQLite). Auto-detects your database dialect from DATABASE_URL.
npm run maker module:make-model postspnpm maker module:make-model postsyarn maker module:make-model postsbun maker module:make-model postsmodule:make-seeder <module> [name]
Generate a seeder file at database/seeders/<name>.ts.
If a model called <name> exists in that module, the seeder is wired to it: the model is imported, table is exported, and the insert loop is pre-filled. When <name> does not match a model but the module has its own model, that one is used instead and the CLI says so.
If no matching model exists, the seeder is still generated, as a standalone seeder that imports no model and inserts nothing until you fill it in. The CLI prints what it found and what to do next:
bun maker module:make-seeder game-suite xyzNo model 'xyz' in module game-suite, so this is a standalone seeder: it imports
no model and writes nothing until you fill it in.
Models in this module: abc. Run module:make-seeder game-suite <one of these>
for a seeder with the model already imported.
Seeder ready: game-suite/xyzThis is deliberate. A seeder does not have to belong to the module that owns the table it writes to, and does not have to write to only one table — so the three reasonable requests below are all supported:
# 1. seed a table owned by ANOTHER module: generate here, then import that
# module's model yourself
bun maker module:make-seeder game-suite posts
# 2. assemble rows from several models: same thing, several imports
bun maker module:make-seeder game-suite sign-up-flow
# 3. seed a table whose model does not exist yet
bun maker module:make-seeder game-suite post-commentsA standalone seeder lives in the module you named, so module:seed <module> is what runs it. If you want module:seed posts to seed the posts table, generate the seeder in posts instead — that keeps "which module owns this seeder" and "which table does it write" aligned. Every other generator makes the same trade when its dependency is missing: a controller with no service falls back to a standalone shape, and a service with no model falls back to a plain one.
npm run maker module:make-seeder postspnpm maker module:make-seeder postsyarn maker module:make-seeder postsbun maker module:make-seeder postsmodule:make-schema <module> [name]
Generate a schema file at schemas/<name>.ts. The generated set depends on the OPEN_API setting at scaffold time — full (item, create, update, id-params and response schemas) or minimal (create, update and id-params only).
npm run maker module:make-schema posts
npm run maker module:make-schema posts publishpnpm maker module:make-schema posts
pnpm maker module:make-schema posts publishyarn maker module:make-schema posts
yarn maker module:make-schema posts publishbun maker module:make-schema posts
bun maker module:make-schema posts publishmodule:make-service <module> [name]
Generate a service file at services/<name>.ts. The service is Drizzle-backed when database/models/<name>.ts exists or when --with-model is passed; otherwise it ships with TODO placeholders where the queries go.
npm run maker module:make-service posts
npm run maker module:make-service posts publishpnpm maker module:make-service posts
pnpm maker module:make-service posts publishyarn maker module:make-service posts
yarn maker module:make-service posts publishbun maker module:make-service posts
bun maker module:make-service posts publish
bun maker module:make-service posts publish --with-modelmodule:make-helper <module> [name]
Generate a helper file at helpers/<name>.ts — pure logic with no I/O, safe to call from controllers, services, jobs and other helpers.
npm run maker module:make-helper posts formatpnpm maker module:make-helper posts formatyarn maker module:make-helper posts formatbun maker module:make-helper posts formatmodule:make-middleware <module> [name]
Generate a module-local middleware file at middlewares/<name>.ts. Register it on a route or route group inside routes/*.ts.
npm run maker module:make-middleware posts auth-checkpnpm maker module:make-middleware posts auth-checkyarn maker module:make-middleware posts auth-checkbun maker module:make-middleware posts auth-checkmodule:make-provider <module> [name]
Generate a provider at providers/<name>.ts inside the module. Module providers are imported automatically at boot, so the module can offer an implementation to the whole application without the application knowing anything about the module's internals.
npm run maker module:make-provider billing stripenpm run maker module:make-provider billing stripe -- --force| Option | Alias | Description |
|---|---|---|
--force | --yes | Overwrite an existing provider file (fails otherwise) |
--dry-run | — | Validate the name and print what would be created without writing |
See Provider Commands for the app-wide counterpart.
module:make-type <module> [name]
Generate a types file at types/<name>.ts with Record, Result and ListResult interfaces for the module to share.
npm run maker module:make-type posts dtopnpm maker module:make-type posts dtoyarn maker module:make-type posts dtobun maker module:make-type posts dtomodule:make-job <module> [name]
Generate a queue job file with the shouldQueue pattern.
npm run maker module:make-job posts publishpnpm maker module:make-job posts publishyarn maker module:make-job posts publishbun maker module:make-job posts publishmodule:make-console <module> [name]
Generate a scheduler/console command file with the defineSchedule pattern.
npm run maker module:make-console posts cleanuppnpm maker module:make-console posts cleanupyarn maker module:make-console posts cleanupbun maker module:make-console posts cleanupmodule:make-test <module> [name]
Generate a unit test file for a module. If no name given, uses the module name. The test file is created under the module's __tests__/ directory.
npm run maker module:make-test posts
npm run maker module:make-test posts user-testpnpm maker module:make-test posts
pnpm maker module:make-test posts user-testyarn maker module:make-test posts
yarn maker module:make-test posts user-testbun maker module:make-test posts
bun maker module:make-test posts user-testDelete Commands
module:delete <name>
Soft-delete a module by moving it to src/storage/trash/modules/<name>-<timestamp>/. The module can be recovered from trash.
npm run maker module:delete posts
npm run maker module:delete posts -- --yes
npm run maker module:delete posts -- --dry-runpnpm maker module:delete posts
pnpm maker module:delete posts --yes
pnpm maker module:delete posts --dry-runyarn maker module:delete posts
yarn maker module:delete posts --yes
yarn maker module:delete posts --dry-runbun maker module:delete posts
bun maker module:delete posts --yes
bun maker module:delete posts --dry-runmodule:delete-notification [name]
Remove the notification backend module (moves to trash). UI files are user-managed. The notifications table stays in the database; remove it with a migration if you no longer need the data.
npm run maker module:delete-notification
npm run maker module:delete-notification -- --yes
npm run maker module:delete-notification -- --dry-runpnpm maker module:delete-notification
pnpm maker module:delete-notification --yes
pnpm maker module:delete-notification --dry-runyarn maker module:delete-notification
yarn maker module:delete-notification --yes
yarn maker module:delete-notification --dry-runbun maker module:delete-notification
bun maker module:delete-notification --yes
bun maker module:delete-notification --dry-runmodule:trash:clean [name]
Permanently remove entries from module trash. Without a name, cleans all trash. With a name, cleans only matching entries.
npm run maker module:trash:clean
npm run maker module:trash:clean posts
npm run maker module:trash:clean -- --yes
npm run maker module:trash:clean -- --dry-runpnpm maker module:trash:clean
pnpm maker module:trash:clean posts
pnpm maker module:trash:clean --yes
pnpm maker module:trash:clean --dry-runyarn maker module:trash:clean
yarn maker module:trash:clean posts
yarn maker module:trash:clean --yes
yarn maker module:trash:clean --dry-runbun maker module:trash:clean
bun maker module:trash:clean posts
bun maker module:trash:clean --yes
bun maker module:trash:clean --dry-runUtility Commands
module:list
List all discovered modules under src/modules/.
npm run maker module:listpnpm maker module:listyarn maker module:listbun maker module:listmodule:seed <module>
Run seeders for a single module.
npm run maker module:seed postspnpm maker module:seed postsyarn maker module:seed postsbun maker module:seed postsmodule:migrate <module>
Regenerate src/database/schema.ts from every module model, create a migration, and run it. The full schema is used so the migration adds the module's tables without dropping tables owned by other modules. Run it after generating a module that owns a table, such as module:make-notification.
npm run maker module:migrate postspnpm maker module:migrate postsyarn maker module:migrate postsbun maker module:migrate postsPanels
Every module command accepts --path=<panel> to nest the module under a panel folder. A panel is only a folder separator — the module keeps the same structure and rules, and its routes mount under the panel path:
npm run maker module:make post -- --path=admin
npm run maker module:make-controller blog post -- --path=admin
npm run maker module:make-route blog post -- --path=admin
npm run maker module:make-model blog post -- --path=adminpnpm maker module:make post --path=admin
pnpm maker module:make-controller blog post --path=admin
pnpm maker module:make-route blog post --path=admin
pnpm maker module:make-model blog post --path=adminyarn maker module:make post --path=admin
yarn maker module:make-controller blog post --path=admin
yarn maker module:make-route blog post --path=admin
yarn maker module:make-model blog post --path=adminbun maker module:make post --path=admin
bun maker module:make-controller blog post --path=admin
bun maker module:make-route blog post --path=admin
bun maker module:make-model blog post --path=adminWithout --path | With --path=admin |
|---|---|
src/modules/post/ | src/modules/admin/post/ |
mounted at /api/post | mounted at /api/admin/post |
Scalar tag Post | Scalar tag Admin / Post |
Nested panels work too: --path=admin/reporting → src/modules/admin/reporting/post/ → /api/admin/reporting/post.
The CLI prints the panel and its API path after scaffolding:
Module ready: admin/post
Panel: admin -> /api/admin/postTo address an existing panel module with any subcommand, pass the same --path — e.g. module:make-controller blog post --path=admin writes src/modules/admin/blog/controllers/post.ts, and module:delete post --path=admin moves src/modules/admin/post/ to trash.
Removed Commands
module:example [name] (removed in 4.1.0)
The one-shot example module generator was removed. Create a module with module:make and add the pieces you need with the component commands above.
