API with NestJS #70. Defining dynamic modules

JavaScript NestJS

This entry is part 70 of 77 in the API with NestJS

So far in this series, we’ve defined many different modules that can group provides and controllers. None of them was customizable, though. In this article, we learn how to create dynamic modules that are customizable, making them easier to reuse. While doing that, we rewrite some of our old code to use dynamic modules.

If you want to know more about modules in general, check out API with NestJS #6. Looking into dependency injection and modules

To better illustrate why we might want to use dynamic modules, let’s look at the we’ve defined in the API with NestJS #25. Sending scheduled emails with cron and Nodemailer article.

email.module.ts

The above module contains the provider and exports it. Let’s look under the hood:

email.service.ts

The crucial thing to notice above is that our will always have the same configuration. So, for example, we can’t have a separate email for password confirmation and a separate email for the newsletter. To deal with this issue, we can create a dynamic module.

Creating a dynamic module

To create a dynamic module, let’s add a static method to the .

email.module.ts

Above, instead of putting our module definition inside the decorator, we return it from the method. We allow the user to provide the as the argument to the function.

emailOptions.interface.ts

We also define a unique constant in a separate file.

constants.ts

We can also use a Symbol.

In the method, we add the to the list of providers along with the option’s value. Thanks to the above, the configuration will be available in the through the decorator.

email.service.ts

Thanks to our approach, we can now configure the module when importing it.

emailConfirmation.module.ts

Asynchronous configuration

There is a significant drawback to the above approach because we no longer can use the when setting up our email provider. Fortunately, we can deal with this issue by creating an asynchronous version of our method. It will have access to the dependency injection mechanism built into NestJS.

Our method will also receive the options, but the type used for its argument is slightly different than .

Let’s go through all of the properties above:

  • – a list of modules we want the to import because we need them in ,
  • – a list of providers we want NestJS to inject into the context of the function,
  • – a function that returns the value for our provider.

A straightforward way to create a type with the above properties is to use the and interfaces.

emailOptions.type.ts

Please notice that is generic and we pass to it to enforce the correct configuration.

Once we have the above, we can define our method.

email.module.ts

Under the hood, our method is very similar to . The difference is that it uses and instead of . It also accepts an array of additional modules to import through .

Thanks to creating a way to configure the asynchronously, we can now use it with various configurations depending on the use case and still use the dependency injection mechanism.

Naming conventions

So far, we’ve only defined the and methods. It is significant to notice that NestJS doesn’t enforce any naming conventions but has some guidelines.

register and registerAsync

The and methods we’ve used in this article so far are supposed to configure a module for use only with the module that imports it.

To illustrate it, let’s look at the .

emailScheduling.module.ts

Above, we provide the configuration for the that we only want to use in the .

forRoot and forRootAsync

With and methods, we aim to configure a dynamic module once and reuse this configuration in multiple places. Therefore, it makes a lot of sense with global modules.

A great example is the module provided by NestJS.

database.module.ts

forFeature and forFeatureAsync

With the and methods, we can alter the configuration specified using . Again, a good example is the .

categories.module.ts

Above, we specify additional configuration using that’s applicable only in the .

Configurable module builder

Configuring a dynamic module can be difficult, especially with the asynchronous methods. Fortunately, NestJS has a class that does much of the work for us. To use it, let’s create a separate file.

email.module-definition.ts

Now, we need our to extend the class.

email.module.ts

Thanks to the above, our allows us to use both and classes.

For it to work as expected, we need to remember to use the constant that we got from the file.

Extending auto-generated methods

If we need additional logic, we can extend the auto-generated methods. When doing that, the can come in handy.

Using methods other than register and registerAsync

If we want to use methods other than and , we need to call the function.

email.module-definition.ts

Summary

In this article, we’ve gone through the idea of dynamic modules. To illustrate it, we implemented it manually and learned how it works under the hood. Then, we’ve learned how to use utilities built into NestJS that can simplify this process significantly. Dynamic modules are a handy feature that allows us to make our modules customizable while taking advantage of the dependency injection mechanism.

Series Navigation<< API with NestJS #69. Database migrations with TypeORMAPI with NestJS #71. Introduction to feature flags >>
Subscribe
Notify of
guest
1 Comment
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Lieselot
Lieselot
1 month ago

Thank you!