Skip to content

Modules

Modules are Gland's composition boundaries. They register controllers, channels and other modules, and carry the lifecycle hooks.

A module is a class that groups controllers and channels, can import other modules, and can react to the application lifecycle. It is the unit you organise code around and the unit the framework walks at startup.

import { Module } from '@glandjs/common'
@Module({
imports: [ProductModule, AuthModule],
controllers: [ProductController, HealthController],
channels: [ProductChannel, AuditChannel],
})
export class AppModule {}

All three keys are optional.

Key Holds Instantiated
imports module classes, dynamic modules, or promises of them once per graph node
controllers classes with route decorators once, as a singleton
channels classes with @On() handlers once, as a singleton

Imports are resolved transitively, and a module is only ever registered once, no matter how many modules import it. A diamond in the graph converges on a single instance.

src/app.module.ts
@Module({
imports: [ProductModule, OrderModule, HealthModule],
channels: [Database],
})
export class AppModule {}
AppModule
┌───┴───────┐
ProductModule OrderModule (both import HealthModule,
│ │ HealthModule is created once)
└─────┬──────┘
HealthModule

A dynamic module extends a decorated module at import time. Its controllers and channels are merged with the base module’s, and its imports are added alongside the base’s.

import type { DynamicModule } from '@glandjs/common'
@Module({ controllers: [CoreController] })
export class CoreModule {}
export function withAuditChannel(audit: Constructor): DynamicModule {
return {
module: CoreModule,
channels: [audit],
}
}
@Module({ imports: [withAuditChannel(AuditChannel)] })
export class AppModule {}

When two modules would import each other, the cycle breaks at module resolution time. Wrap the import in a promise to defer it.

@Module({
imports: [Promise.resolve(() => require('./lazy').LazyModule)],
})
export class AppModule {}

Any class in the graph can implement the five lifecycle hooks. On a module they describe application-level concerns.

@Module({ imports: [ProductModule] })
export class AppModule implements OnModuleInit, OnModuleDestroy, OnAppBootstrap, OnAppShutdown {
onModuleInit() {} // after the graph is built
onAppBootstrap() {} // everything is bound
onAppShutdown(signal?: string) {} // SIGTERM, SIGINT, …
onModuleDestroy() {} // last thing that runs
}

See Lifecycle events for the full contract and the exact order.

The root module is the argument to GlandFactory.create().

import { GlandFactory } from '@glandjs/core'
import { AppModule } from './app.module'
const app = await GlandFactory.create(AppModule)
interface ModuleMetadata<T = any> {
imports?: ImportableModule<T>[]
controllers?: Constructor<T>[]
channels?: Constructor<T>[]
}
type ImportableModule<T = any> =
| Constructor<T>
| DynamicModule<T>
| Promise<DynamicModule<T>>

GlandFactory.create(root: Constructor): Promise<GlandBroker>

Section titled “GlandFactory.create(root: Constructor): Promise<GlandBroker>”

Walks the module graph, builds the container, runs the hooks and binds routes and channels. Returns a GlandBroker — see @glandjs/core.