Node.js Framework Series — 1.2.2. NestJS — Modules
Modules are another important one of the NestJS building blocks. We can isolate our component by using module classes. When we create new object in our application, NestJS don’t know it at first. To initialise and use it in our application, we need to register our objects to NestJS by using @Module() decorators. We can create a module object for each component as a separate class (suggested way), or also you can register all of objects in the app.mudule.ts class. Let’s have a look app.module.ts class as shown below:
app.mudule.ts
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { LoginController } from './login/login.controller';
import { RegisterController } from './register/register.controller';
import { ProfileController } from './profile/profile.controller';@Module({
imports: [],
controllers: [AppController, LoginController, RegisterController, ProfileController],
providers: [AppService],
})
export class AppModule {}
As you see above, we register all of our classes, providers/services in our modules. In fact, if you create your modules, classes, providers by using NestJS CLI tool, they will be registered to appropriate module automatically. Therefore, better to use NestJS CLI tool for object creations.
For example, let’s create new component with NestJS CLI tool. To create a new component, first we create a module. Then create a controller. Finally create a provider/service. NestJS CLI tool will create objects and register them to right places.
If you follow above sequence, it will create an object as follows:
notification.module.ts
import { Module } from '@nestjs/common';
import { NotificationController } from './notification.controller';
import { NotificationService } from './notification.service';@Module({
controllers: [NotificationController],
providers: [NotificationService]
})
export class NotificationModule {}
and app.module.ts as shown below:
@Module({
imports: [NotificationModule],
controllers: [AppController, LoginController, RegisterController, ProfileController],
providers: [AppService],
})
export class AppModule {}
As you see above, NestJS CLI, first it created folder as a ‘notification’, then it created module, controller, service classes in it. Finally it registered this new component to app.module.ts class by using imports. Now it is much better, app.module.ts won’t be complex. Every component will manage its dependencies by using their own module classes. So, all of the components will be strict, isolated and decoupled within the application.
Let’s have a look at the @Module() decorator, and as you see above, it takes single object. It’s properties as follows:
- providers : the providers that will be instantiated by the Nest injector and that may be shared at least across this module. Providers are encapsulated by module by default.
- controllers : the set of controllers defined in this module which have to be instantiated
- imports : the list of imported modules that export the providers which are required in this module
- exports : the subset of providers that are provided by this module and should be available in other modules which import this module
We are going to cover the following topics for module features:
- Feature modules
- Shared modules
- Module re-exporting
- Dependency injection
- Global modules
Feature modules
We created notification component above by using NestJS CLI Tool. As you see in there, it created a folder at first, then put everything in it. It created feature module, too. It encapsulated this component’s items by default with this feature module. There is more about it. We are able to put all related codes together, keep code organized and establishing the clear boundaries which they are important for the SOLID[1] and DRY[2] code principles. So, we can manage our code complexity and work in a team easily.
Shared Module
In NestJS, modules are singletons by default, and encapsulates providers by default, too. But sometimes we need to share our providers with other components/ modules. For example, in our example we created register, profile, login modules and then we created notification module, too. We need to use this notification service in other modules to send a notification such as to send a ‘I forgot my email/password’ email or a ‘your account is activated successful’ email. Therefore, we need to share our notification with others. We can do it as shown below:
Modules can be shared easily and every module is a shared module by default. We can register modules to any other modules and then reused. As you see above, we need to share and use our notification.service.ts in other modules. To do so, we will export notification.service.ts as follows :
notification.module.ts
import { Module } from '@nestjs/common';
import { NotificationController } from './notification.controller';
import { NotificationService } from './notification.service';@Module({
controllers: [NotificationController],
providers: [NotificationService],
exports: [NotificationService],
})
export class NotificationModule {}
That’s all, our notification.service.ts class ready use in other modules.
Module re-exporting
Modules can export their internal providers as you see above, also they can re-export modules that you import as follows:
@Module({
imports: [CommonModule],
exports: [CommonModule],
})
export class CoreModule {}
Dependency Injection[3]
We can inject providers to module class as well(e.g., for configuration purpose)
notification.module.ts
import { Module } from '@nestjs/common';
import { NotificationController } from './notification.controller';
import { NotificationService } from './notification.service';@Module({
controllers: [NotificationController],
providers: [NotificationService],
})
export class NotificationModule {
constructor(private notificationService: NotificationService) {}
}
Global modules
When you want to provide a service(e.g., database connections, audit logs, notifications, etc.) which should be available in everywhere, we can do it simply with the @Global() decorator as shown below:
notification.module.ts
import { Module, Global } from '@nestjs/common';
import { NotificationController } from './notification.controller';
import { NotificationService } from './notification.service';@Global()
@Module({
controllers: [NotificationController],
providers: [NotificationService],
exports: [NotificationService],
})
export class NotificationModule {}
Resources:
[1] SOLID principles, 2020, available at https://en.wikipedia.org/wiki/SOLID
[2] DRY code, 2020, available at https://en.wikipedia.org/wiki/Don%27t_repeat_yourself
[3] Dependency injection, 2020, available at https://en.wikipedia.org/wiki/Dependency_injection