Skip to main content

Function for creating a NestJS-mod module

The createNestModule function for creating a dynamic NestJS module with the ability to configure through configurations or environment variables, as well as providing the ability to use part of the module’s services through the forFeature method or transfer part of the configurations from the module feature.

All modules have the ability to create several parallel named instances of the module with different input parameters; to do this, you need to pass the name of the instance to the contextName option.

Unlike NestJS modules, NestJS-mod modules can contain additional wrapper methods that will be called when building a NestJS application.

An example of creating a native NestJS module using the createNestModule function and passing various types of configurations

import {
ConfigModel,
ConfigModelProperty,
EnvModel,
EnvModelProperty,
createNestModule,
getNestModuleDecorators,
InjectableFeatureConfigurationType,
} from "@nestjs-mod/common";
import { Injectable } from "@nestjs/common";
import { NestFactory } from "@nestjs/core";
import { IsNotEmpty } from "class-validator";

// App1Module

const { InjectFeatures } = getNestModuleDecorators({
moduleName: "App1Module",
});

@ConfigModel()
class AppFeatureConfig {
@ConfigModelProperty()
@IsNotEmpty()
featureOptionConfig!: string;
}

@Injectable()
class AppFeaturesService {
constructor(
@InjectFeatures()
private readonly appFeatureConfigs: InjectableFeatureConfigurationType<AppFeatureConfig>[]
) {}

getFeatureConfigs() {
return this.appFeatureConfigs.map(
({ featureConfiguration }) => featureConfiguration
);
}
}

const { App1Module } = createNestModule({
moduleName: "App1Module",
sharedProviders: [AppFeaturesService],
featureConfigurationModel: AppFeatureConfig,
});

@ConfigModel()
class App2Config {
@ConfigModelProperty()
@IsNotEmpty()
option!: string;
}

@Injectable()
class App2Service {
constructor(
private readonly appFeaturesService: AppFeaturesService,
private readonly app2Config: App2Config
) {}

getFeatureConfigs() {
return this.appFeaturesService.getFeatureConfigs();
}

getConfig() {
return this.app2Config;
}
}

// App2Module

const { App2Module } = createNestModule({
moduleName: "App2Module",
imports: [
App1Module.forFeature({
featureModuleName: "App2Module",
featureConfiguration: { featureOptionConfig: "featureOptionConfig-app2" },
}),
],
providers: [App2Service],
configurationModel: App2Config,
});

@EnvModel()
class App3Env {
@EnvModelProperty()
@IsNotEmpty()
option!: string;
}

@Injectable()
class App3Service {
constructor(
private readonly appFeaturesService: AppFeaturesService,
private readonly app3Env: App3Env
) {}

getFeatureConfigs() {
return this.appFeaturesService.getFeatureConfigs();
}

getEnv() {
return this.app3Env;
}
}

const { App3Module } = createNestModule({
moduleName: "App3Module",
imports: [
App1Module.forFeature({
featureModuleName: "App2Module",
featureConfiguration: { featureOptionConfig: "featureOptionConfig-app3" },
}),
],
providers: [App3Service],
environmentsModel: App3Env,
});

// Test

const { AppModule } = createNestModule({
moduleName: "AppModule",
imports: [
App1Module.forRoot(),
App2Module.forRoot({ configuration: { option: "appConfig3value" } }),
App3Module.forRoot({ environments: { option: "appEnv2value" } }),
],
});

async function bootstrap() {
const app = await NestFactory.create(AppModule.forRoot());
const appFeatureScannerService = app.get(AppFeaturesService);
const app2Service = app.get(App2Service);
const app3Service = app.get(App3Service);

console.log(appFeatureScannerService.getFeatureConfigs()); // output: [{ featureOptionConfig: 'featureOptionConfig-app2' }, { featureOptionConfig: 'featureOptionConfig-app3' }]
console.log(app2Service.getFeatureConfigs()); // output: [{ featureOptionConfig: 'featureOptionConfig-app2' }, { featureOptionConfig: 'featureOptionConfig-app3' }]
console.log(app3Service.getFeatureConfigs()); // output: [{ featureOptionConfig: 'featureOptionConfig-app2' }, { featureOptionConfig: 'featureOptionConfig-app3' }]
console.log(app2Service.getConfig()); // output: { option: 'appConfig3value' }
console.log(app3Service.getEnv()); // output: { option: 'appEnv2value' }
}

bootstrap();

Wrapper methods

When creating a module, you can pass many different new parameters, there are both original NestJS options and extended ones.

Descriptions and examples of using each option will be discussed in separate posts; now I will only describe the options for configuring the module.

Options for creating a NestJS-mod module

environmentsModel

The class and its properties are marked with decorators of the type “Environment Variables” (Env model), it contains properties with primitive types used in the module, the values of which can be obtained from various sources, such as: process.env or consul-kv .

configurationModel

The class and its properties are marked with decorators of the “Config model” type; the properties of primitive and complex types that are used in the module, the values for which must be passed when connecting the module to the application, are described in the code.

staticEnvironmentsModel

The “Environment Variables” class (Env model) with static properties of a primitive type can be used at the time of generating module metadata; values can be obtained from various sources, for example: process.env or consul-kv.

Example: different import conditions depending on environment variables, dynamic routes for REST controllers.

staticConfigurationModel

A configuration class with static properties of primitive and complex types that can be used when generating module metadata (imports, controllers); values for them must be passed when connecting the module to the application.

featureEnvironmentsModel

Environment variables are a “feature” of modules with primitive types, the values of which can be obtained from various sources, such as: process.env or consul-kv.

Example: the name of the environment variable for connecting to the database feature of the module differs from the name of the environment variable of the root connection.

featureConfigurationModel

A class for the “feature” of modules, variables of primitive and complex types that can be added to the current module from other modules.

Example: the transport for sending a message can be defined as a product “feature” functionality, but the main implementation of bypassing recipients and sending will be a “core” or “integration” module.

Create the main dynamic module and pass the asynchronous configuration

When the configuration of a module is unknown in advance, we can pass it using an asynchronous factory; if other modules are needed for its operation, we can pass them through the imports option, just like in regular NestJS.

In addition to the asynchronous factory, you can also use class and pass by value, just like in regular NestJS.

NestJS-mod has another way of passing a configuration, this is passing an Observable stream with the values of this configuration. This method is needed when values may change over time.

Example: core module for dynamically changing the address and credentials of a proxy server when the current one is blocked, the module feature may not call an additional method to obtain the current address and credentials, it simply uses the configuration instance that was connected through the constructor, as if it were a static configuration.

Working with options that were passed from other modules (configuration feature)

Since at the time of initialization of NestJS modules, the order of loading (resolving) may differ, definitely at the moment of start we can get all the “configuration features” only in the NestJS onApplicationBootstrap hook.

To obtain it you need to use the InjectFeatures decorator. When the application is running (runtime), there are no longer any problems with access to all configurations.

Decorators for working with module entities

Since the use of decorators is static code and cannot be changed in real time, decorators for each module must be created manually using the getNestModuleDecorators function

Types of decorators:

InjectService

To connect a provider using an injected token or class.

Example: if an application has several instances of the same module, but with different context names, then the name of this context can be passed to the decorator.

InjectFeatures

To connect an array with all configurations obtained from various modules.

Example: There is a “systems” website accessibility module and there is a configuration feature through which the “core” module for working with the database can notify about the functionality of the database. Based on this list of “systems,” the module decides whether people can be allowed into the backend.

InjectAllFeatures

If the application has several instances of a module with different contexts, then to get all the “feature configurations” you need to use this decorator.

InjectFeatureEnvironments

In addition to configurations, you can also get all the environment variables that the feature modules used.

Example: a certain console application that, at startup, will create all the necessary databases on the database server, we receive the connection string to the database with root rights from the “systems” of the module, and the connection string to the database of the feature modules themselves is passed from the feature modules themselves and at the same time All key names are different and must be validated when starting the application.

InjectAllFeatureEnvironments

The same as InjectFeatureEnvironments only collects information on all instances of the module.

InjectModuleSettings

Sometimes you need to get all the metadata of configuration classes and classes for module environment variables, this is a decorator for that.

Example: the application collects all configuration values of environment variables with the names of the keys and generates a report.

InjectAllModuleSettings

Context-independent retrieval of all module configuration metadata.