In this post, you will learn how to create a NestJS Dynamic Module from scratch.
Here are the specific things you are going to learn:
- Why dynamic modules?
- How to use a dynamic module in NestJS?
- How to create a dynamic module and customize the provider?
Most of the application code that we write makes use of regular or static modules. However, dynamic modules are also a key part of the NestJS fundamentals.
You can think of modules as the scope for certain functionalities within your application. In case you are new to the concept, do check out my detailed post on the NestJS Module System.
1 – Why Dynamic Modules?
Static modules are sufficient for most use cases.
But they have a limitation.
Static modules stay the same, no matter which module is consuming them. The consumer module cannot influence the behaviour of the static module’s providers.
But why do you need to influence the behaviour of a static module?
A simple use case could be the need to have different database parameters for different deployments.
You can also think of this as a plugin-based approach to building software. A customizable generic plugin can be used by many systems just by tweaking the configuration parameters.
In a typical NestJS application, you can use dynamic modules to handle stuff like configurations. These configurations can be anything from database settings to file system details and so on.
The consuming module can simply call an API provided by the dynamic module to tweak the configuration according to its requirement.
INFO
Dynamic Modules basically provide an API for importing one module to another. This API helps customize the properties and behaviour of the module while importing. This is different from static bindings where no such customization is possible.
2 – Using a NestJS Dynamic Module
Let’s create a sample dynamic module that can help understand the concept in more detail.
However, before actually creating a module, you must understand how to use a dynamic module.
Below is an example of how to use a simple static module.
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsModule } from './products/products.module';
import { ConfigurationModule } from './configuration/configuration.module';
@Module({
imports: [ProductsModule, ConfigurationModule],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
As you can see, you are importing the ConfigurationModule
into the AppModule
. This approach is also known as static module binding.
Let’s look at how to use a dynamic module.
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { ProductsModule } from './products/products.module';
import { ConfigurationModule } from './configuration/configuration.module';
@Module({
imports: [ProductsModule,
ConfigurationModule.register({db_url: 'http://localhost:27017'})],
controllers: [AppController],
providers: [AppService],
})
export class AppModule {}
Can you spot the difference between the static and dynamic approaches?
In the dynamic approach, the ConfigurationModule
is a normal typescript class with a method named register()
.
Basically, register()
is a static method attached to the ConfigurationModule
class. By convention, it is good practice to call this method register()
or forRoot()
. Technically, it can have any arbitrary name.
You accept some arguments in the register()
method. You can pass these arguments as a configuration object. In the above example, the configuration object contains the property db_url
.
? Important Point
Every item within the imports
array needs to be a module in order to make it available. Therefore, we can conclude that the register()
method also returns a module.
In fact, the register()
method actually returns a dynamic module that we can configure based on the input configuration object.
Dynamic Modules basically provide an API for importing one module to another. This API helps customize the properties and behaviour of the module while importing. This is different from static bindings where no such customization is possible.
3 – Creating a dynamic module in NestJS
Within our project, you need to create a separate folder named configuration
for the ConfigurationModule
.
Within this folder, create a file named configuration.module.ts
that contains the module definition along with the code for the register()
method.
Check out the below snippet.
import { DynamicModule, Module } from "@nestjs/common";
import { ConfigurationService } from "./configuration.service";
@Module({})
export class ConfigurationModule {
static register(options): DynamicModule {
return {
module: ConfigurationModule,
providers: [
{
provide: 'CONFIGURATION_OPTIONS',
useValue: options
},
ConfigurationService
],
exports: [ConfigurationService]
}
}
}
Basically, you are defining the static register()
method.
This method takes the options
object as input and returns an instance of DynamicModule
. The DynamicModule
has the same properties you might have noticed within the @Module()
decorator. However, you pass a blank object to the @Module()
decorator.
Also, notice the providers
array.
Apart from the ConfigurationService
, you also have something known as CONFIGURATION_OPTIONS
. Basically, you are assigning the options
object to the useValue
attribute. We will be using this in the ConfigurationService
.
Below is the code for the ConfigurationService
in the configuration.service.ts
file.
import { Inject, Injectable } from "@nestjs/common";
@Injectable()
export class ConfigurationService {
private readonly db_url;
constructor(@Inject('CONFIGURATION_OPTIONS') private options) {
this.db_url = options.db_url;
}
getDBUrl(): string {
return this.db_url;
}
}
The first thing to note here is the constructor. You inject the CONFIGURATION_OPTIONS
within the constructor. This helps us follow the principle of Dependency Injection.
Next, you assign the db_url
from the options object to the private db_url
of the service class.
For demonstration, you can implement a method named getDBUrl()
. This method simply returns the current value of the db_url
.
A real application might use the db_url
value for database configuration. But we are not doing that right now. We are only trying to understand the anatomy of a dynamic module.
You can now use the ConfigurationService
within the AppModule
. More precisely, you use it in the AppController
.
import { Controller, Get } from '@nestjs/common';
import { AppService } from './app.service';
import { ConfigurationService } from './configuration/configuration.service';
@Controller()
export class AppController {
constructor(private readonly appService: AppService,
private readonly configService: ConfigurationService) {}
@Get()
getHello(): string {
return this.appService.getHello();
}
@Get("/config")
getConfiguration(): string {
return this.configService.getDBUrl();
}
}
Here, you inject the ConfigurationService
within the AppController
class. Next, you make a call to the getDBUrl()
method within the getConfiguration
route handler.
When you start the application and make a request to http://localhost:3000/config
, the output will be http://localhost:27017
.
Basically, the dynamic ConfigurationModule
returns the injected value. You can tweak the dynamic module from the calling module based on your requirements.
Conclusion
The code for this chapter is available on GitHub. You can download it and play around with it to practice the concepts discussed in this post.
Here’s a summary of what you have learnt:
- Why dynamic modules?
- How to use a dynamic module in NestJS?
- How to create a dynamic module and customize the provider?
While we saw a very rudimentary example, it was more of an attempt to understand the concept of dynamic modules. These types of modules will come in extremely useful in some of the more complicated requirements when you need to deal with real database configurations.
If you have any comments or queries, please write in the comments section below.
NestJS is a fundamental pillar in my Cloud & Backend learning path. To know more, don’t forget to subscribe to the Progressive Coder newsletter.
Also, say ‘Hi’ on Twitter for more real-time updates on what’s happening at Progressive Coder.
7 Comments
Husna · September 24, 2021 at 1:16 am
thank you so much for Nestjs posts.
may you have to retype filename in `class AppModule` snippet to `app.module.ts`, where over there you put `configuration.module.ts`.
Saurabh Dashora · September 25, 2021 at 4:08 am
Thanks for pointing it out! Corrected the filename now.
Glad that the posts on NestJS are able to help!
Javad · April 3, 2022 at 1:44 am
Thank you, it’s very useful for me
Saurabh Dashora · April 4, 2022 at 6:31 am
Thanks for the kind feedback!
Jimmy · May 14, 2022 at 2:22 pm
Thank you! Really clear and helpful for me!
Saurabh Dashora · May 23, 2022 at 1:36 am
Hi Jimmy…Thanks for the great feedback! Happy to help.
Jimmy · May 14, 2022 at 2:23 pm
Thank you! Really helpful and clear!