ReferenceClass Runtime

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

PropertyTypeDescription
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

PropertyTypeDescription
provideTokenToken the provider is registered under (class, string, or symbol)
useClass?ConstructorConstruct this class to satisfy the token
useValue?unknownBind this constant value to the token (scope is ignored)
useFactory?(...args: unknown[]) => unknownCall 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

PropertyTypeDescription
importsFunction[]Always present, defaults to []
controllersFunction[]Always present, defaults to []
providersModuleProvider[]Always present, defaults to []
exportsFunction[]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

PropertyTypeDescription
appApplicationThe app instance. Must have a router.
rootModuleFunctionThe root @Module class
options?ModuleRegistrationOptionsRegistration options

ModuleRegistrationOptions

PropertyTypeDescription
prefix?stringRoute prefix applied to all controllers
middleware?Middleware[]Global middleware applied to all routes
container?ContainerExplicit container — wins over isolate and app.container
isolate?booleanUse a fresh, isolated container instead of app.container/global
validate?booleanEagerly validate the DI graph before serving requests
debug?booleanEnable 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

  1. options.container, if provided.
  2. A fresh isolated container, if options.isolate is true.
  3. app.container, if set.
  4. The global container.

Graph utilities

Lower-level building blocks used internally by registerModule:

import { collectModuleGraph, collectModuleControllers } from 'nextrush/class';
  • collectModuleGraph(rootModule) — walks imports in post-order, returning every module in the graph exactly once (diamond imports deduplicated, cycles skipped rather than recursed).
  • collectModuleControllers(modules) — flattens and deduplicates every controllers entry 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';

Was this helpful?

On this page