DEV Community

Jayvee Ramos
Jayvee Ramos

Posted on

2. Building a Common Module for Microservices with NestJS and MongoDB

Introduction

In this tutorial, we will walk you through the process of creating a common module for your microservices using NestJS and MongoDB. The common module will include a persistence layer to work with a database and an ORM (Object-Relational Mapping) to interact with the database. We'll build it in a generic and abstract way, following best practices so that it can be easily reused across different microservices.

Prerequisites

Before getting started, make sure you have the following prerequisites: (Proceed to first tutorials)

  • Node.js and npm installed on your machine.
  • NestJS CLI installed.
  • MongoDB installed and running.

Step 1: Installing Dependencies

The first step is to install the necessary dependencies for your common module. Open your terminal and run the following commands:

npm install @nestjs/mongoose mongoose
npm install @nestjs/config
Enter fullscreen mode Exit fullscreen mode

These dependencies include NestJS Mongoose for MongoDB integration and NestJS Config for managing environment variables.

Step 2: Generating Common Modules

Next, use the Nest CLI to generate the common modules. Ensure that you have a nest-cli.json file that lists the available projects. In this example, we'll use the project named "common."

nest generate module -p common database
nest generate module -p common config
Enter fullscreen mode Exit fullscreen mode

This generates two modules: database and config.

Step 3: Config Module Setup

In the config module, we want to create a wrapper around the NestJS Config module, allowing us to load environment variables from a .env file.

Inside the config.module.ts file, set up the imports array as follows:

import { Module } from '@nestjs/common';
import {
  ConfigService,
  ConfigModule as NestConfigModule,
} from '@nestjs/config';

@Module({
  imports: [NestConfigModule.forRoot()],
  providers: [ConfigService],
  exports: [ConfigService],
})
export class ConfigModule {}
Enter fullscreen mode Exit fullscreen mode

Code Explanation

It defines a Nest.js module called ConfigModule. In Nest.js, modules are a way to encapsulate different parts of your application and organize related functionality.

Within the @Module decorator, it specifies the module's configuration:

The imports property is an array containing NestConfigModule.forRoot(). This means that the ConfigModule depends on the NestConfigModule, which is used to load and manage configuration settings from environment variables or configuration files.

The providers property is an array containing ConfigService.

ConfigService is a component that can be injected into other parts of the application to access configuration values.

The exports property is an array containing ConfigService. This makes the ConfigService available for injection into other modules when they import the ConfigModule.

Step 4: Database Module Setup

Ensure that you have already created your database in MongoDB. In my case, I'm using MongoDB Compass locally, so my connection should look like this: mongodb://localhost:27017. I have named my database 'reservation,' so my complete connection URL is: mongodb://localhost:27017/reservation.

If you're unsure how to create a database in MongoDB, take a moment to research it, and then return to this tutorial. Once you've created your MongoDB database, simply copy the URL and add the name of your database at the end, just as I did.

After that, create a .env file inside the root of our directory by using the command touch .env. Then, create a variable named MONGODB_URI and set your connection URL as its value. It should look like this: MONGODB_URI=mongodb://localhost:27017/reservation

Then in the database module, you'll set up a connection to MongoDB. Update the database.module.ts file as follows:

import { Module } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { ModelDefinition, MongooseModule } from '@nestjs/mongoose';
import { ConfigModule } from '../config';

@Module({
  imports: [
    MongooseModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: (configService: ConfigService) => ({
        uri: configService.get('MONGODB_URI'),
      }),
      inject: [ConfigService],
    }),
  ],
})
export class DatabaseModule {
  static forFeature(models: ModelDefinition[]) {
    return MongooseModule.forFeature(models);
  }
}


Enter fullscreen mode Exit fullscreen mode

This code sets up a database module in a Nest.js application, specifically configuring the MongoDB connection using Mongoose. The configuration for the MongoDB connection is obtained asynchronously from the ConfigService provided by the ConfigModule, making it easy to manage and adapt the database connection settings by modifying the application's configuration.

DatabaseModule provides a convenient way to register Mongoose models within a Nest.js application. The forFeature method simplifies the process of making these models available for use in the module by delegating the registration to MongooseModule. This can be helpful for organizing and managing database-related features in a modular and maintainable way.

Note: Don't worry if you cannot understand it yet; we will be diving into it later in this tutorial.

Step 5: Exporting the Common Modules

Barrel Export
to streamline the process of importing various components, making code more organized and improving readability we are going to implement A "barrel export technique" it is a technique in software development used to simplify and centralize the import/export structure of a project, primarily in JavaScript and TypeScript. It involves creating a central module, often named index.js, that re-exports the contents of other modules within the same directory or related parts of a project.

To do that, let's create an index.ts file in our database and config module. Just run the following commands in your terminal:

touch libs/common/src/config/index.ts
touch libs/common/src/database/index.ts

Now, populate the config index with the following code:

export * from './config.module';
Enter fullscreen mode Exit fullscreen mode

And populate the database index with the following code:

export * from './database.module';
Enter fullscreen mode Exit fullscreen mode

In order to use the common modules in your microservices, you should export them from the libs/common/src/index.ts file of your common module. Populate the libs/common/src/index.ts with the following code:

export * from './config';
export * from './database';

Enter fullscreen mode Exit fullscreen mode

Now you can easily import the DatabaseModule from your microservices.

To test our setup let's import our DatabaseModul into our src/app.module.ts

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { DatabaseModule } from '@app/common';

@Module({
  imports: [DatabaseModule],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

then run npm run start:dev

you should see something like this

[Nest] 15280  - 10/26/2023, 11:35:05 AM     LOG [NestFactory] Starting Nest application...
[Nest] 15280  - 10/26/2023, 11:35:06 AM     LOG [InstanceLoader] DatabaseModule dependencies initialized +25ms
[Nest] 15280  - 10/26/2023, 11:35:06 AM     LOG [InstanceLoader] MongooseModule dependencies initialized +1ms
[Nest] 15280  - 10/26/2023, 11:35:06 AM     LOG [InstanceLoader] ConfigHostModule dependencies initialized +1ms
[Nest] 15280  - 10/26/2023, 11:35:06 AM     LOG [InstanceLoader] AppModule dependencies initialized +1ms
[Nest] 15280  - 10/26/2023, 11:35:06 AM     LOG [InstanceLoader] ConfigModule dependencies initialized +0ms
[Nest] 15280  - 10/26/2023, 11:35:06 AM     LOG [InstanceLoader] ConfigModule dependencies initialized +1ms
[Nest] 15280  - 10/26/2023, 11:35:06 AM     LOG [InstanceLoader] MongooseCoreModule dependencies initialized +37ms
[Nest] 15280  - 10/26/2023, 11:35:06 AM     LOG [RoutesResolver] AppController {/}: +30ms
[Nest] 15280  - 10/26/2023, 11:35:06 AM     LOG [RouterExplorer] Mapped {/, GET} route +3ms
[Nest] 15280  - 10/26/2023, 11:35:06 AM     LOG [NestApplication] Nest application successfully started +2ms
Enter fullscreen mode Exit fullscreen mode

it means we successfully initialized our database

Step 6: Validation of Environment Variables

To ensure that your microservices will fail to start if necessary environment variables are missing, you can set up validation. Stop your development server and install the joi package:
npm install joi

In the config.module.ts file, add validation for the required environment variables
update your libs/common/src/config/config.module.ts

import { Module } from '@nestjs/common';
import {
  ConfigService,
  ConfigModule as NestConfigModule,
} from '@nestjs/config';
import * as Joi from 'joi';

@Module({
  imports: [
    NestConfigModule.forRoot({
      validationSchema: Joi.object({
        MONGODB_URI: Joi.string().required(),
      }),
    }),
  ],
  providers: [ConfigService],
  exports: [ConfigService],
})
export class ConfigModule {}

Enter fullscreen mode Exit fullscreen mode

This schema ensures that the MONGODB_URI environment variable is required. If it's missing, the application will fail to start.

Conclusion

You've successfully set up a common module for your microservices using NestJS and MongoDB. This module includes a persistence layer and the ability to manage environment variables. By following best practices and abstracting third-party dependencies, you've created a foundation for building scalable and maintainable microservices. In the next part of this tutorial, we'll continue building the abstract repository for data access in your microservices. Stay tuned!

Top comments (0)