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

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.


Saurabh Dashora

Saurabh is a Software Architect with over 12 years of experience. He has worked on large-scale distributed systems across various domains and organizations. He is also a passionate Technical Writer and loves sharing knowledge in the community.

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!

Leave a Reply

Your email address will not be published. Required fields are marked *