Module scaffolding
Every feature lives under app/<mod>/ as a self-contained module. There is no autoloader — preloads, import aliases, migration paths, tsconfig includes, and event listener mounts are all wired explicitly. Miss any of these and the module simply won't load, its rows won't migrate, or its event types won't typecheck.
Quick start — make:module
Do not wire by hand unless you have to. The generator creates the skeleton and injects every touchpoint idempotently:
node ace make:module <mod> # routes + controller + ui page + smoke test
node ace make:module <mod> --db --i18n --events
--db — adds database/migrations/ and registers its path in config/database.ts.
--i18n — adds resources/lang/{en,fr,pt}/<mod>.json and registers the i18n loader.
--events — adds start/events.ts + types/events.ts, preloads events, and adds the types/** glob to the core tsconfig (skip that and the EventsList merge breaks the client typecheck).
Always wired: the #<mod>/* alias, the #<mod>/routes preload, and the ui/** tsconfig include. After running it, pnpm dev (or any node ace) regenerates route/page types, then pnpm typecheck. The rest of this skill is what the command does under the hood — use it to add a piece later or to wire a module by hand.
Conventions
- Module dir tree & existing modules — see AGENTS.md → "Architecture — module per bounded context" (always in context). Create only the pieces you need;
routes.ts is the only mandatory file.
- Aliases go in
apps/web/package.json → "imports". Add "#<mod>/*": "./app/<mod>/*.js".
- Preloads go in
apps/web/adonisrc.ts → preloads. Always add () => import('#<mod>/routes') at minimum. Add #<mod>/start/events if the module emits or listens. Add #<mod>/start/view if it mounts edge templates.
- Migrations/seeders paths append to
apps/web/config/database.ts → connections.postgres.migrations.paths and seeders.paths.
- tsconfig include — when the module has
ui/, add ../../<mod>/ui/**/*.ts and ../../<mod>/ui/**/*.tsx to apps/web/app/core/ui/tsconfig.json → include. Add ../../<mod>/types/**/*.ts when it has types/events.ts, or the EventsList merge is invisible to the client tsc and event typechecks break.
- i18n — if the module has UI strings, drop JSON in
resources/lang/{en,fr,pt}/<mod>.json. Three locales are canonical; all three must exist.
Repo refs
- Preloads registration:
apps/web/adonisrc.ts → preloads.
- Import aliases:
apps/web/package.json → "imports" (all #mod/* entries).
- Migration paths:
apps/web/config/database.ts → migrations.paths.
- tsconfig include (client typecheck):
apps/web/app/core/ui/tsconfig.json → include (see the auth/users ui/** + types/** entries).
- Generator:
apps/web/commands/make_module.ts.
- Smallest module — just
routes.ts + a controller; add other layers only as the module needs them.
- Full-featured module (actions, policies, transformers, migrations, seeders, tests):
apps/web/app/users/.
- Module with events + notifications:
apps/web/app/users/start/events.ts + apps/web/app/users/notifications/user_welcome_notification.ts.
Workflow
Bootstrap a new module <mod>
Prefer node ace make:module <mod> [--db] [--i18n] [--events] (see Quick start). The steps below are the by-hand equivalent, and what you follow to add a piece to an existing module.
- Create the dir tree — only the pieces you'll actually use.
routes.ts is the one mandatory file.
- Add the alias to
apps/web/package.json:"imports": {
...
"#<mod>/*": "./app/<mod>/*.js",
...
}
- Register preload(s) in
apps/web/adonisrc.ts:preloads: [
...
() => import('#<mod>/routes'),
// add if the module emits / listens to events
() => import('#<mod>/start/events'),
// add if the module has edge templates
() => import('#<mod>/start/view'),
]
- If there are migrations, append to
apps/web/config/database.ts:migrations: {
paths: [
'app/users/database/migrations',
'app/notifications/database/migrations',
'app/<mod>/database/migrations',
],
}
- If there are seeders, append to
seeders.paths similarly.
- If it has
ui/ (or types/events.ts), add the includes to apps/web/app/core/ui/tsconfig.json:"include": [
...
"../../<mod>/ui/**/*.ts",
"../../<mod>/ui/**/*.tsx",
"../../<mod>/types/**/*.ts", // only if the module has types/events.ts
...
]
- If it has i18n keys, create
app/<mod>/resources/lang/{en,fr,pt}/<mod>.json. See [[i18n]].
- If it has UI, follow [[inertia]] for the page resolver and [[layout-shells]] for which shell to import.
- If it needs CRUD, follow [[crud]] to fill in the controller / actions / validators / policy / transformer / tests.
- Run typecheck —
pnpm typecheck — before writing more code, so alias/preload/tsconfig wiring is verified early.
- Run migration if you added tables —
pnpm ace migration:run (or migration:fresh if it's the first pass and no data matters).
Add a route to an existing module
Just edit app/<mod>/routes.ts. router.get/post/... calls in that file are picked up because the file is preloaded.
Delete a module cleanly
Reverse the wiring in the same order — remove preload, alias, migration path, tsconfig include, then delete the dir. Skipping the alias/preload cleanup leaves phantom imports that will fail at boot.
Anti-patterns
- ❌ Assuming autoloader — everything is explicit. If it's not in the alias + preload, it doesn't exist.
- ❌ Adding migrations without appending to
config/database.ts — migrations won't run.
- ❌ Adding
types/events.ts without its types/** glob in app/core/ui/tsconfig.json — the EventsList merge is invisible to the client tsc and event typechecks break.
- ❌ Adding two modules with overlapping aliases (
#user/* vs #users/*) — pick one.
- ❌ Copying a whole module and forgetting to update the alias references inside — every internal import in a copied module still points to the source.
- ❌ Creating
routes.ts but not preloading it — routes silently disappear.
Related skills
[[crud]] · [[actions-events]] · [[testing]] · [[migrations]] · [[i18n]] · [[inertia]]
1---2name: module-scaffolding3description: How to add a new module to this AdonisJS monorepo — prefer `node ace make:module`, which scaffolds and wires every touchpoint. User-invoked via /module-scaffolding when wiring a module by hand or adding a piece later.4license: MIT5---67# Module scaffolding89Every feature lives under `app/<mod>/` as a self-contained module. There is no autoloader — preloads, import aliases, migration paths, tsconfig includes, and event listener mounts are all wired explicitly. Miss any of these and the module simply won't load, its rows won't migrate, or its event types won't typecheck.1011## Quick start — `make:module`1213Do not wire by hand unless you have to. The generator creates the skeleton and injects every touchpoint idempotently:1415```bash16node ace make:module <mod> # routes + controller + ui page + smoke test17node ace make:module <mod> --db --i18n --events18```1920- `--db` — adds `database/migrations/` and registers its path in `config/database.ts`.21- `--i18n` — adds `resources/lang/{en,fr,pt}/<mod>.json` and registers the i18n loader.22- `--events` — adds `start/events.ts` + `types/events.ts`, preloads events, **and** adds the `types/**` glob to the core tsconfig (skip that and the `EventsList` merge breaks the client typecheck).2324Always wired: the `#<mod>/*` alias, the `#<mod>/routes` preload, and the `ui/**` tsconfig include. After running it, `pnpm dev` (or any `node ace`) regenerates route/page types, then `pnpm typecheck`. The rest of this skill is what the command does under the hood — use it to add a piece later or to wire a module by hand.2526## Conventions2728- **Module dir tree & existing modules** — see AGENTS.md → "Architecture — module per bounded context" (always in context). Create only the pieces you need; `routes.ts` is the only mandatory file.29- **Aliases go in** `apps/web/package.json` → `"imports"`. Add `"#<mod>/*": "./app/<mod>/*.js"`.30- **Preloads go in** `apps/web/adonisrc.ts` → `preloads`. Always add `() => import('#<mod>/routes')` at minimum. Add `#<mod>/start/events` if the module emits or listens. Add `#<mod>/start/view` if it mounts edge templates.31- **Migrations/seeders** paths append to `apps/web/config/database.ts` → `connections.postgres.migrations.paths` and `seeders.paths`.32- **tsconfig include** — when the module has `ui/`, add `../../<mod>/ui/**/*.ts` and `../../<mod>/ui/**/*.tsx` to `apps/web/app/core/ui/tsconfig.json` → `include`. Add `../../<mod>/types/**/*.ts` when it has `types/events.ts`, or the `EventsList` merge is invisible to the client tsc and event typechecks break.33- **i18n** — if the module has UI strings, drop JSON in `resources/lang/{en,fr,pt}/<mod>.json`. Three locales are canonical; all three must exist.3435## Repo refs3637- Preloads registration: `apps/web/adonisrc.ts` → `preloads`.38- Import aliases: `apps/web/package.json` → `"imports"` (all `#mod/*` entries).39- Migration paths: `apps/web/config/database.ts` → `migrations.paths`.40- tsconfig include (client typecheck): `apps/web/app/core/ui/tsconfig.json` → `include` (see the `auth`/`users` `ui/**` + `types/**` entries).41- Generator: `apps/web/commands/make_module.ts`.42- Smallest module — just `routes.ts` + a controller; add other layers only as the module needs them.43- Full-featured module (actions, policies, transformers, migrations, seeders, tests): `apps/web/app/users/`.44- Module with events + notifications: `apps/web/app/users/start/events.ts` + `apps/web/app/users/notifications/user_welcome_notification.ts`.4546## Workflow4748### Bootstrap a new module `<mod>`4950Prefer `node ace make:module <mod> [--db] [--i18n] [--events]` (see Quick start). The steps below are the by-hand equivalent, and what you follow to add a piece to an existing module.51521. **Create the dir tree** — only the pieces you'll actually use. `routes.ts` is the one mandatory file.532. **Add the alias** to `apps/web/package.json`:54 ```json55 "imports": {56 ...57 "#<mod>/*": "./app/<mod>/*.js",58 ...59 }60 ```613. **Register preload(s)** in `apps/web/adonisrc.ts`:62 ```ts63 preloads: [64 ...65 () => import('#<mod>/routes'),66 // add if the module emits / listens to events67 () => import('#<mod>/start/events'),68 // add if the module has edge templates69 () => import('#<mod>/start/view'),70 ]71 ```724. **If there are migrations**, append to `apps/web/config/database.ts`:73 ```ts74 migrations: {75 paths: [76 'app/users/database/migrations',77 'app/notifications/database/migrations',78 'app/<mod>/database/migrations',79 ],80 }81 ```825. **If there are seeders**, append to `seeders.paths` similarly.836. **If it has `ui/` (or `types/events.ts`)**, add the includes to `apps/web/app/core/ui/tsconfig.json`:84 ```jsonc85 "include": [86 ...87 "../../<mod>/ui/**/*.ts",88 "../../<mod>/ui/**/*.tsx",89 "../../<mod>/types/**/*.ts", // only if the module has types/events.ts90 ...91 ]92 ```937. **If it has i18n keys**, create `app/<mod>/resources/lang/{en,fr,pt}/<mod>.json`. See [[i18n]].948. **If it has UI**, follow [[inertia]] for the page resolver and [[layout-shells]] for which shell to import.959. **If it needs CRUD**, follow [[crud]] to fill in the controller / actions / validators / policy / transformer / tests.9610. **Run typecheck** — `pnpm typecheck` — before writing more code, so alias/preload/tsconfig wiring is verified early.9711. **Run migration** if you added tables — `pnpm ace migration:run` (or `migration:fresh` if it's the first pass and no data matters).9899### Add a route to an existing module100101Just edit `app/<mod>/routes.ts`. `router.get/post/...` calls in that file are picked up because the file is preloaded.102103### Delete a module cleanly104105Reverse the wiring in the same order — remove preload, alias, migration path, tsconfig include, then delete the dir. Skipping the alias/preload cleanup leaves phantom imports that will fail at boot.106107## Anti-patterns108109- ❌ Assuming autoloader — everything is explicit. If it's not in the alias + preload, it doesn't exist.110- ❌ Adding migrations without appending to `config/database.ts` — migrations won't run.111- ❌ Adding `types/events.ts` without its `types/**` glob in `app/core/ui/tsconfig.json` — the `EventsList` merge is invisible to the client tsc and event typechecks break.112- ❌ Adding two modules with overlapping aliases (`#user/*` vs `#users/*`) — pick one.113- ❌ Copying a whole module and forgetting to update the alias references inside — every internal import in a copied module still points to the source.114- ❌ Creating `routes.ts` but not preloading it — routes silently disappear.115116## Related skills117118[[crud]] · [[actions-events]] · [[testing]] · [[migrations]] · [[i18n]] · [[inertia]]