Create NestJS Microservices using RabbitMQ - Part 2
Tags: microservices, nestjs, rabbitmq, typescript
Published: 2023-03-14 18:48:31

​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](https://www.prisma.io/).

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

```bash
docker-compose.yml
```
now, we can start creating forgot password APIs for user service.

in main.ts (user service) - bootstrap function
```typescript
  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');
```

and in app.controller.ts (user service) create a controller for forgot password API.
```typescript
 @Public()
  @Post('/forgot-password')
  forgotPassword(@Body() data: ForgotPasswordDto): Promise<void> {
    return this.appService.sendForgotPasswordEmail(data);
  }
```
and in app.service.ts (user service) 
```typescript
 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;
    }
  }
```
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.

```typescript
  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');
```
now, define mailer service in user service. in import of app.module.ts (user service)

```typescript
 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],
      },
    ]),
```
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.

```typescript
 constructor(
    @Inject('MAIL_SERVICE') private readonly mailClient: ClientProxy,
    private prisma: PrismaService,
  ) {
    this.mailClient.connect();
  }
``` 

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

```typescript
  @EventPattern('send_email')
  public sendEmailPattern(@Payload() data): void {
    this.appService.sendEmail(data);
  }
```
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.

```typescript
  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));
  }
```

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](https://github.com/BackendWorks/nestjs-microservices). 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!