Modules
@Module decorator and registerModule() — signatures, options, and provider config.
@Module and registerModule() API reference — signatures, options, and provider
configuration shapes. For the problem modules solve, the mental model, and how the import
graph is walked, see Modules concepts.
Installation
$ pnpm add nextrush
Or install @nextrush/class directly:
$ pnpm add @nextrush/class
@Module(options)
Class decorator. Declares a module's imports, controllers, providers, and exports.
Signature:
function Module(options?: ModuleOptions): ClassDecorator;ModuleOptions
| Property | Type | Description |
|---|---|---|
imports? | Function[] | Other @Module classes this module composes |
controllers? | Function[] | @Controller classes owned by this module |
providers? | ModuleProvider[] | Services/values/factories this module registers |
exports? | Function[] | Providers this module makes visible to importers. Recorded on metadata, not enforced yet. |
import { Module, Controller, Get, Service } from 'nextrush/class';
@Service()
class UserService {
findAll() {
return [{ id: 1, name: 'Alice' }];
}
}
@Controller('/users')
class UserController {
constructor(private users: UserService) {}
@Get() findAll() { return this.users.findAll(); }
}
@Module({
controllers: [UserController],
providers: [UserService],
})
class UserModule {}ModuleProvider
A provider is either a bare class constructor, or a ModuleProviderConfig binding a token.
type ModuleProvider = Function | ModuleProviderConfig;ModuleProviderConfig
| Property | Type | Description |
|---|---|---|
provide | Token | Token the provider is registered under (class, string, or symbol) |
useClass? | Constructor | Construct this class to satisfy the token |
useValue? | unknown | Bind this constant value to the token (scope is ignored) |
useFactory? | (...args: unknown[]) => unknown | Call this factory to produce the value |
inject? | Token[] | Tokens resolved and passed (in order) as arguments to useFactory |
scope | 'singleton' | 'transient' | 'request'= 'singleton' | Lifecycle scope |
Exactly one of useClass / useValue / useFactory should be set per config provider.
@Module({
providers: [
// Bare class — uses its own @Service scope, or singleton if undecorated
UserService,
// Value provider
{ provide: 'CONFIG', useValue: { port: 8080 } },
// Factory provider
{
provide: 'DB',
useFactory: (config: Config) => createConnection(config),
inject: ['CONFIG'],
},
],
})
class AppModule {}getModuleMetadata(target)
Read the normalized ModuleMetadata off a @Module-decorated class. Returns undefined if
the class has no module metadata.
function getModuleMetadata(target: Function): ModuleMetadata | undefined;ModuleMetadata
| Property | Type | Description |
|---|---|---|
imports | Function[] | Always present, defaults to [] |
controllers | Function[] | Always present, defaults to [] |
providers | ModuleProvider[] | Always present, defaults to [] |
exports | Function[] | Always present, defaults to [] |
isModule(target)
function isModule(target: Function): boolean;Returns true if target carries @Module metadata.
registerModule(app, rootModule, options?)
Wires a whole @Module graph in one call: walks imports, registers every module's providers
into a DI container, then hands the flattened controller list to the same
registerControllers pipeline.
Signature:
function registerModule(
app: Application,
rootModule: Function,
options?: ModuleRegistrationOptions
): Promise<void>;Parameters
| Property | Type | Description |
|---|---|---|
app | Application | The app instance. Must have a router. |
rootModule | Function | The root @Module class |
options? | ModuleRegistrationOptions | Registration options |
ModuleRegistrationOptions
| Property | Type | Description |
|---|---|---|
prefix? | string | Route prefix applied to all controllers |
middleware? | Middleware[] | Global middleware applied to all routes |
container? | Container | Explicit container — wins over isolate and app.container |
isolate? | boolean | Use a fresh, isolated container instead of app.container/global |
validate? | boolean | Eagerly validate the DI graph before serving requests |
debug? | boolean | Enable debug logging for discovery and registration |
Returns: Promise<void> — resolves once every module's providers are registered and every
controller across the graph has its routes registered on app.router.
Throws: NotAModuleError if rootModule or any class in an imports array lacks
@Module metadata.
import { createApp, listen } from 'nextrush';
import { Module, registerModule } from 'nextrush/class';
@Module({ imports: [UserModule, BillingModule] })
class AppModule {}
const app = createApp();
await registerModule(app, AppModule, { prefix: '/api' });
await listen(app, 8080);Container selection order
options.container, if provided.- A fresh isolated container, if
options.isolateistrue. app.container, if set.- The global container.
Graph utilities
Lower-level building blocks used internally by registerModule:
import { collectModuleGraph, collectModuleControllers } from 'nextrush/class';collectModuleGraph(rootModule)— walksimportsin post-order, returning every module in the graph exactly once (diamond imports deduplicated, cycles skipped rather than recursed).collectModuleControllers(modules)— flattens and deduplicates everycontrollersentry across a list of modules.
TypeScript Exports
Complete import reference
import { Module, getModuleMetadata, isModule, registerModule } from 'nextrush/class';
import { collectModuleControllers, collectModuleGraph } from 'nextrush/class';
import type {
ModuleMetadata,
ModuleOptions,
ModuleProvider,
ModuleProviderConfig,
ModuleRegistrationOptions,
} from 'nextrush/class';Related
- Modules concepts — Mental model, import-graph walk, trade-offs
- Controllers — The registrar
registerModuledelegates to - Dependency Injection — Provider scopes and the container
- @nextrush/class overview — How the class runtime fits together