DEV Community

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

Posted on • Edited on

Create NestJS Microservices using RabbitMQ - Part 2

In the previous blog, we learned the basics of communication between two services using NestJS and RabbitMQ. In this blog, we will explore another concept in NestJS: the Event pattern. Using event patterns, we can create a worker for a microservice that will handle jobs created by other services.

This example will help you understand basic communication using event patterns. We will use Prisma as an ORM for the PostgreSQL database. Prisma is a type-safe ORM that provides built-in support for TypeScript types and generates migrations. Check out the Prisma documentation for more details.

First, let’s start up the core services from the previous blog using the docker-compose.yml file.

docker-compose up
Enter fullscreen mode Exit fullscreen mode

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

Creating Forgot Password APIs for the User Service

In the main.ts file of the user service, update the 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);
logger.log('User service started successfully');
Enter fullscreen mode Exit fullscreen mode

In the app.controller.ts file of the user service, create a controller for the forgot password API:

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

In the app.service.ts file of the user service, implement the sendForgotPasswordEmail function:

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 use mailClient with the emit function to send a payload to the connected service. Unlike send, emit does not expect a response.

Creating the Mailer Service

Use the NestJS CLI to create a new mailer service. In the main.ts file of the 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

In the user service, define the mailer service dependency in the app.module.ts file:

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

In the app.service.ts file of the user service, inject and connect the mailer service:

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

In the app.controller.ts file of the mailer service, define the send_email event pattern:

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

In the app.service.ts file of the mailer service, create an AWS SES instance and a function for sending emails. Update the templatePath as per your 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

The mailer service does not have any open ports since it does not expose any APIs. This service will send forgot password token links to users via email.

Scalability Considerations

Using RabbitMQ as the connection between services, we can scale RabbitMQ on AWS to handle increased incoming requests. This ensures sufficient computing power is available to process the mailer queue’s requests.

Complete Example

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

Thanks for reading this. If you have any queries, feel free to email me at harsh.make1998@gmail.com.

Until next time!

Top comments (1)

Collapse
 
saiarkar profile image
Sai Ar Kar

How to handle if the consumer server was down or something went wrong in consumer service?