Skip to main content

Module

The Module in Nest.js is the most difficult and inconvenient part of the framework, but it is also the core part. There is no way to replace this part, so we have to make some tools to help us use it more easily.

Auto Dependency Injection (AutoDI)

Nest.js Extends Toolkit provides a set of decorators for loading controllers, providers and Sequelize models automatically:

export type AutoControllerOptions = {
path: string[];
};
@AutoController(options: AutoControllerOptions);

export type AutoProviderOptions = {
path: string[];
export?: boolean;
};
@AutoProvider(options: AutoProviderOptions);

export type AutoSequelizeModelOptions = {
connection?: string;
path: string[];
};
@AutoSequelizeModel(options: AutoSequelizeModelOptions);

Auto load controllers

AutoController is the simplest decorator, it loads classes from the specified path and adds it to the Module.controllers metadata array.

@AutoController({
path: [
path.join("./controllers/**/*.js")
]
})
@Module({})
export class DemoModule {}

Auto load providers

AutoProvider is similar to AutoController, except that the loaded classes are added to Module.providers. Also, there is an additional option export?: boolean, if export is true, AutoProvider will also add the auto-loaded classes to the Module.exports metadata array. If you want to make the module global, you should set export to true.

@AutoProvider({
path: [
path.join("./services/**/*.js")
],
export: true,
})
@Global()
@Module({})
export class DemoModule {}

Auto load Sequelize models

To begin using it, we first install the required dependencies.

npm install --save @nestjs/sequelize sequelize sequelize-typescript
npm install --save-dev @types/sequelize

The implementation of AutoSequelizeModel differs from the first two decorators in that it does not add classes to the module metadata, but uses the SequelizeModule.forFeature method to store auto-loaded models. there is an additional option connection?: string with a default value of DEFAULT_CONNECTION_NAME, note that this value needs to be the same as the name value set in the SequelizeModule.forRoot.

const ROOT_CONNECTION_NAME = "root_connection"

@AutoSequelizeModel({
connection: ROOT_CONNECTION_NAME,
path: [
path.join(__dirname, "./feature1/models/**/*.js"),
],
})
@Module({})
export class Feature1Module {}

@AutoSequelizeModel({
connection: ROOT_CONNECTION_NAME,
path: [
path.join(__dirname, "./feature2/models/**/*.js"),
],
})
@Module({})
export class Feature2Module {}

@Module({
imports: [
SequelizeModule.forRoot({
name: ROOT_CONNECTION_NAME,
dialect: 'postgres',
}),
Feature1Module,
Feature2Module,
],
})
export class RootModule {}

AutoDI path patterns

Pay attention to the extension of source file in path patterns. You should pass in the path to the compiled .js file instead of the path to the .ts source code.

@AutoController({
path: [
path.join("./controllers/**/*.ts") // ❌
]
})
@Module({})
export class DemoModule {}

@AutoController({
path: [
path.join("./controllers/**/*.js") // ✅
]
})
@Module({})
export class DemoModule {}

Decorator Composition

According to Typescript's documentation on decorators 1:

The following steps are performed when evaluating multiple decorators on a single declaration in TypeScript:

  1. The expressions for each decorator are evaluated top-to-bottom.
  2. The results are then called as functions from bottom-to-top.

Until now (Nest.js v9.3.9), the implementation of the Module decorator does not support inheritance from the existing ModuleMetadata data, so if you add AutoDI decorators between @Module and class, these will not have any effect.

// ❌
@Module({})
@AutoProvider({ /** */ })
class DemoModule {}

// ✅
@AutoProvider({ /** */ })
@Module({})
class DemoModule {}