Modules
A feature folder is already a roost module. Most apps never need more than that. When you do
want cross-feature wiring or a chain that applies to a whole folder, drop an optional
*.module.ts and decorate a class with @RoostModule.
// server/features/projects/projects.module.ts
@RoostModule({
guards: [MemberGuard], // applies to every route in projects/
exports: [ProjectsService], // the module's public surface
})
export class ProjectsModule {}
It's named @RoostModule (not @Module) on purpose — a roost module lives in
server/features/** and is a different layer from a Nuxt module or layer.
What it declares
| Field | Effect |
|---|---|
imports | other modules whose exported providers this module may depend on |
exports | which of this module's providers are visible to importing modules |
guards / interceptors / filters | chains applied to every route in the folder, between the global and class levels |
There is intentionally no providers/controllers list — the folder is the discovery unit, so
nothing is hand-maintained — and no forRoot/dynamic form (config lives in nuxt.config).
Module-scoped chains
A chain on the module applies to every route in the folder, so you declare it once instead of
repeating an @UseGuards on each controller. The effective order is:
global → module → class → method
In manifest.json the module-contributed tokens are tagged (module), just as globals are tagged
(global) — so the full effective chain stays visible on disk.
Encapsulation (enforced at build time)
By default any provider can be injected anywhere (the container is flat). A @RoostModule adds a
boundary: a provider in another folder can depend on ProjectsService only if its module
imports the projects module and the projects module exports ProjectsService. Otherwise the
codegen throws:
[nuxt-roost] OrdersService depends on ProjectsService (module 'projects'),
but module 'orders' does not import ProjectsModule. Declare it via
@RoostModule({ imports: [...] }) on the consumer and @RoostModule({ exports: [...] }) on the owner.
_shared/ — and any _-prefixed folder — is implicitly global: its providers (the usual home
for shared guards/filters) are visible everywhere with no import/export.
autoImportFeatures every feature
symbol is in one flat auto-import namespace, so a name is always referenceable; exports controls
only what can be DI-resolved across a module boundary.No runtime cost
The boundary is a compile-time check — the generated awilix container is byte-for-byte what it was without modules (no token namespacing, no per-module child containers). Modules are a build error, not a resolver feature.
One consequence of the global token space: two providers can't share a class name. roost reports that as a clear duplicate-name error rather than letting one silently shadow the other.
Adopt incrementally
Add one *.module.ts at a time. Folders without one keep working exactly as before — modules are
opt-in.