DEV Community

Cover image for Create NestJS Microservices using RabbitMQ - Part 2
Harsh Makwana
Harsh Makwana

Posted on • Updated on

Create NestJS Microservices using RabbitMQ - Part 2

​In the past blog, we have learned the basic communication between two services created with Nestjs and RabbitMQ.

In this blog, we will learn and create an example of another concept of Nestjs, the Event pattern. using event patterns we can create a worker for microservice which will work on the jobs created by other services.

this example will help you to get an idea of basic communication with event patterns.

we will use Prisma as an ORM for the postgres database. Prisma is a type-safe ORM that provides built-in support for typescript types and generated migrations. checkout the documentation here.

First, let's start up core services that we have used in a past blog.

docker-compose.yml
Enter fullscreen mode Exit fullscreen mode

now, we can start creating forgot password APIs for user service.

in main.ts (user service) - bootstrap function

  const logger = new Logger();
  const configService = new ConfigService();
  app.connectMicroservice({
    transport: Transport.RMQ,
    options: {
      urls: [`${configService.get('rb_url')}`],
      queue: `${configService.get('auth_queue')}`,
      queueOptions: { durable: false },
      prefetchCount: 1,
    },
  });
  await app.startAllMicroservices();
  await app.listen(9001);
  console.log('User service started successfully');
Enter fullscreen mode Exit fullscreen mode

and in app.controller.ts (user service) create a controller for forgot password API.

 @Public()
  @Post('/forgot-password')
  forgotPassword(@Body() data: ForgotPasswordDto): Promise<void> {
    return this.appService.sendForgotPasswordEmail(data);
  }
Enter fullscreen mode Exit fullscreen mode

and in app.service.ts (user service)

 public async sendForgotPasswordEmail(data: ForgotPasswordDto) {
    try {
      const { email } = data;
      const user = await this.getUserByEmail(email);
      if (!user) {
        throw new HttpException('user_not_found', HttpStatus.NOT_FOUND);
      }
      const token = nanoid(10);
      await this.prisma.token.create({
        data: {
          expire: new Date(new Date().getTime() + 60000),
          token,
          user: {
            connect: {
              email,
            },
          },
        },
      });
      const payload: IMailPayload = {
        template: 'FORGOT_PASSWORD',
        payload: {
          emails: [email],
          data: {
            firstName: user.first_name,
            lastName: user.last_name,
          },
          subject: 'Forgot Password',
        },
      };
      this.mailClient.emit('send_email', payload);
    } catch (e) {
      throw e;
    }
  }
Enter fullscreen mode Exit fullscreen mode

here we're using mailClient with function emit. using emit we can send payload to the service connected with current microservice.

difference between emit and send is we can not expect something in return while using emit for sending the payload.

now, create a mailer service using NestJS Cli.

in main.ts of mailer service, update the bootstrap function.

  const logger = new Logger();
  const configService = new ConfigService();
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.RMQ,
      options: {
        urls: [`${configService.get('rb_url')}`],
        queue: `${configService.get('mailer_queue')}`,
        queueOptions: { durable: false },
        prefetchCount: 1,
      },
    },
  );
  await app.listen();
  logger.log('Mailer service started successfully');
Enter fullscreen mode Exit fullscreen mode

now, define mailer service in user service. in import of app.module.ts (user service)

 ClientsModule.registerAsync([
      {
        name: 'MAIL_SERVICE',
        imports: [ConfigModule],
        useFactory: (configService: ConfigService) => ({
          transport: Transport.RMQ,
          options: {
            urls: [`${configService.get('rb_url')}`],
            queue: `${configService.get('mailer_queue')}`,
            queueOptions: {
              durable: false,
            },
          },
        }),
        inject: [ConfigService],
      },
    ]),
Enter fullscreen mode Exit fullscreen mode

this will tell user service that we will use mailer services as a dependency service.

in app.service.ts (user service) inject and connect mailer service.

 constructor(
    @Inject('MAIL_SERVICE') private readonly mailClient: ClientProxy,
    private prisma: PrismaService,
  ) {
    this.mailClient.connect();
  }
Enter fullscreen mode Exit fullscreen mode

and now in app.controller.ts (mailer service), define send_email event pattern.

  @EventPattern('send_email')
  public sendEmailPattern(@Payload() data): void {
    this.appService.sendEmail(data);
  }
Enter fullscreen mode Exit fullscreen mode

in app.service.ts (mailer service), create an aws instance and create a function for sending an email.

NOTE: update templatePath as per project structure.

  private ses: SES;
  constructor(private configService: ConfigService) {
    this.logger = new Logger();
    this.ses = new SES({
      ...this.configService.get('aws'),
    });
  }

 async function sendEmail(job: {
    data: { template: EmailTemplates; payload: any };
  }) {
    const { template, payload } = job.data;
    const templatePath = join(
      __dirname,
      './templates/',
      `${EmailTemplates[template]}.html`,
    );
    let _content = readFileSync(templatePath, 'utf-8');
    const compiled = _.template(_content);
    _content = compiled(payload.data);
    this.ses
      .sendEmail({
        Source: this.configService.get('sourceEmail'),
        Destination: {
          ToAddresses: payload.emails,
        },
        Message: {
          Body: {
            Html: {
              Charset: 'UTF-8',
              Data: _content,
            },
          },
          Subject: {
            Charset: 'UTF-8',
            Data: payload.subject,
          },
        },
      })
      .promise()
      .catch((error) => this.logger.error(error));
  }
Enter fullscreen mode Exit fullscreen mode

Here, our mailer service does not have any ports open as there are no APIs contained for mailer service in our example.

this will send emails of forgot password token links to users with the help of mailer service. from here we can look further into the scalability of the patterns we have used here and in a past blog.

here, we have used RabbitMQ as a connection between two services, which is also a Queue service. we can scale RabbitMQ on amazon AWS based on built service. so that if the number of incoming requests from user service will increase then we have enough amount of computing power available for the mailer queue to process incoming requests.

I have created a complete example of microservices using NestJS and RabbitMQ on Github. If you have any suggestions or have any issues, create issues/PR on GitHub.

Thanks for reading this. If you've any queries, feel free to email me at harsh.make1998@gmail.com
Until next time!

Top comments (0)