Implementing Microservices with NestJS and RabbitMQ
Development
2024-09-195 Min Read
Jay Jethava
Jay Jethava

Implementing Microservices with NestJS & RabbitMQ: Ultimate Guide

Jay Jethava
Jay Jethava
  • What is Microservices Architecture?

  • RabbitMQ Tutorial: Setting Up RabbitMQ

  • Implementing Microservices with NestJS

  • Setting Up NestJS Projects

  • Creating the RabbitMQ Service

  • RabbitMQ Service Class Explanation

  • RabbitMQ Service Code

  • Data Sharing By RabbitMq Service 

  • Conclusion

Do you want to develop e-commerce applications that can scale and be effective? Or are you curious about how using microservices architecture can help achieve this?

In this blog post, we will discuss how to implement a microservices architecture using NestJS and RabbitMQ. The implementation will be demonstrated through the example of an e-commerce application.

Let’s dive in!

What is Microservices Architecture?

What is Microservices Architecture

The architecture of microservices is used to break down an application into smaller, independent services that can be developed, deployed, and scaled separately. 

This is done through RabbitMQ, a message broker that allows them to communicate asynchronously with each other, is loosely coupled, and resilient.


RabbitMQ Tutorial: Setting Up RabbitMQ

Setting Up RabbitMQ

  1. Sign up at CloudAMQP
  2. Log into your account
  3. Click on “Create New Instance.”
  4. Select a plan (including the free “Little Lemur” tier if applicable).
  5. Set the instance name and region.
  6. Click “Create Instance.”
  7. Open the instance and get the connection URL from the AMQP details.

Implementing Microservices with NestJS

Let’s consider an e-commerce application as an example. In this application, we’ll have multiple microservices, such as:

Implementing Microservices with NestJS

  1. Order Service: Handles order creation and management.
  2.  Notification Service: Sends notifications to users about their orders.

Each service will run on a different Nest project and communicate through RabbitMQ.

Workflow

Order Service

  • The front end sends a request to the Order Service’s /create-order endpoint.
  • Validates the request and creates an order in the database.
  • Publishes an event to the order notification queue via RabbitMQ to send an order confirmation notification.

  Notification Service

  • Listens to the order notification queue.
  • Receives the message about the new order.
  • Sends an order confirmation notification (e.g., email) to the user.

Setting Up NestJS Projects

Now let’s go through the steps for the implementation. 

Install Dependencies

First, install the necessary dependencies:

npm install @nestjs/common @nestjs/core amqplib

Generate Nest Projects

  • Generate a Nest Project with an order module with a service method (createOrder) for creating new orders.
  • Generate a Nest Project with a notification module with a service method (sendNotification) for sending notifications.

Creating the RabbitMQ Service

1.  Add a Module

  • In both projects, add a module named rabbit-mq.
  • Declare the module as @Global().

Creating the RabbitMQ Service

2. Define the RabbitMQ Service

  • Create a file named rabbit-mq.service.ts.
  • This file will contain a well-structured class, RabbitMqService, for managing RabbitMQ connections and channels using NestJS.

RabbitMQ Service Class Explanation

The RabbitMqService class handles RabbitMQ operations in a structured way. 

Here's a breakdown of its functionality:

1. Initialization

  • Connection and Channel Setup: When instantiated, the service establishes a connection and creates a channel to RabbitMQ.
  • Flexible Configuration: The connection URL is passed via the constructor, allowing easy configuration changes.
  • Initialization Method: The init method handles the setup, ensuring everything is ready to use.

2. Publish Method

  • Queue Assertion: Before sending a message, the service asserts the queue to ensure it exists.
  • Message Conversion: Messages are converted to JSON strings before being sent.

3. Subscribe Method

  • Queue Assertion: Before consuming messages, the service asserts the queue.
  • Message Handling: Received messages are parsed from JSON and passed to a callback function.

4. Initialization Check

  • Ensure Initialization: The ensureInitialized method checks if the service is ready, waiting for the init method to complete if not.

5. Graceful Shutdown

  • Cleanup: The onModuleDestroy method closes the RabbitMQ connection and channel when the service is destroyed, ensuring a clean shutdown.

RabbitMQ Service Code

import { Injectable, OnModuleDestroy } from '@nestjs/common';

import { connect, Channel, Connection } from 'amqplib';



@Injectable()

export class RabbitMqService implements OnModuleDestroy {

  private connection: Connection;

  private channel: Channel;

  private initialized = false;

  private initPromise: Promise<void>;



  constructor(private readonly connectionUrl: string) {

    this.initPromise = this.init();

  }



  private async init() {

    try {

      if (this.connection && this.channel) {

        console.log('RabbitMq connection already established.');

        return;

      }

      this.connection = await connect(this.connectionUrl);

      this.channel = await this.connection.createChannel();

      this.initialized = true;

      console.log('RabbitMq connection established.');

    } catch (error) {

      console.error('Failed to establish RabbitMq connection:', error);

      throw error; // Consider implementing a retry strategy here.

    }

  }



  async publish(queue: string, message: any) {

    await this.ensureInitialized();

    try {

      await this.channel.assertQueue(queue);

      this.channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)));

    } catch (error) {

      console.error(`Failed to publish message to queue ${queue}:`, error);

    }

  }



  async subscribe(queue: string, callback: (message: any) => void) {

    await this.ensureInitialized();

    try {

      await this.channel.assertQueue(queue);

      this.channel.consume(queue, (msg) => {

        if (msg !== null) {

          callback(JSON.parse(msg.content.toString()));

          this.channel.ack(msg);

        }

      });

      console.log(`Successfully subscribed to queue: ${queue}`);

    } catch (error) {

      console.error(`Failed to subscribe to queue ${queue}:`, error);

    }

  }



  private async ensureInitialized() {

    if (!this.initialized) {

      await this.initPromise;

    }

  }



  async onModuleDestroy() {

    try {

      await this.channel.close();

      await this.connection.close();

      console.log('RabbitMq connection closed.');

    } catch (error) {

      console.error('Failed to close RabbitMq connection:', error);

    }

  }

}

Data Sharing By RabbitMq Service 

Now, we will share data between order and notification microservices through the RabbitMQ service. 

Data Sharing By RabbitMq Service

In our example, the data flows from the order service to the notification service. Hence, the notification service that is receiving data will have to subscribe to a queue on which data will be published by the other services (i.e. order service).

Subscribe Queue

Here, when the notification service module is initialized, it will subscribe to a queue ‘order-notification’ and bind a method ‘sendNotification’ with that.

Means, that whenever data is published to the ‘order-notification’ queue, the notification service will receive the data and will call the ‘send notification’ method to take an action (i.e. send a notification to the user mentioned in the data published to the queue)

import { Injectable } from '@nestjs/common';
import { RabbitMqService } from 'src/rabbit-mq/rabbit-mq.service';

@Injectable()
export class NotificationService {
 constructor(private rabbitMQService: RabbitMqService) {}

 async onModuleInit() {
   this.rabbitMQService.subscribe(
     'order-notification',
     this.sendNotification.bind(this),
   );
 }

 async sendNotification(order: object) {
   // Logic to send notification based on the order details
   console.log('🔔 sendNotification is called...');
 }
}

Publish Data

After an order is created in the order service, it will share data to the notification service by publishing the required data to the ‘order notification’.

import { Body, Controller, Post } from '@nestjs/common';
import { OrderService } from './order.service';
import { RabbitMqService } from 'src/rabbit-mq/rabbit-mq.service';

@Controller('order')
export class OrderController {
 constructor(
   private orderService: OrderService,
   private rabbitMQService: RabbitMqService,
 ) {}

 @Post('/new-order')
 createOrder(@Body() body) {
   // < Logic to to create order >
   console.log('Order created successfully...');

   // -> send data to notification service
   this.rabbitMQService.publish('order-notification', body);

   return 'order placed';
 }
}

Conclusion

By utilizing NestJS and RabbitMQ, microservices architecture is established leading to the growth, efficacy, and flexibility of your applications. With the help of this comprehensive guide, you will be able to create a durable and expandible online shopping application.

Regardless of if you are a novice or an expert coder; this manual will assist you in understanding and using microservices in your work.

Happy coding!

Frequently Asked Questions

Is NestJS Better Than Express?

When to Use Kafka vs RabbitMQ?

How to implement microservices in NestJS?

How to use RabbitMQ in microservices?

What is the architecture of NestJS?

What programming language is used in RabbitMQ?

Does RabbitMQ have an API?

How do you integrate microservices in Node.js?

Watch These Videos for More Insights

Related Blogs

Never Miss an Update

Get the latest news, updates, and insights delivered straight to your inbox.