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:
- The expressions for each decorator are evaluated top-to-bottom.
- 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 {}