paint-brush
How to Build a Microservices Architecture with Node.js and RabbitMQ by@wole
187 reads

How to Build a Microservices Architecture with Node.js and RabbitMQ

by Hephzibah AdejumoOctober 1st, 2024
Read on Terminal Reader
Read this story w/o Javascript
tldt arrow

Too Long; Didn't Read

In this article, we will be exploring the concept of Microservice Architecture, and implementing basic microservices-driven software using Node.js and RabbitMQ.
featured image - How to Build a Microservices Architecture with Node.js and RabbitMQ
Hephzibah Adejumo HackerNoon profile picture

Introduction

The microservice architecture is an approach to building scalable software by breaking it down into smaller and independent applications called services. It allows organizations to build resilient, flexible, easy-to-maintain, and scalable software to compete and meet growing demands.


In this article, we will be exploring the concept of Microservice Architecture, and implementing basic microservices-driven software using Node.js and RabbitMQ.

Fundamentals of Microservices Architecture

What are Microservices?

Microservices are a collection of individual and autonomous applications performing a well-defined function, which make up a piece of software. Applications built using this architectural style comprise of smaller independently deployable components, which communicate with each other through message brokers, such as RabbitMQ or other protocols like HTTP. This is a stark contrast to monolithic applications, which are built as a unified entity.

Microservices Architecture vs Monolithic Architecture

As we mentioned earlier, the microservice architecture is used to build software by breaking it down into individual components, which handle specific parts of the business logic, such as user authentication or notification. When a monolithic application is built, there is no segregation; all components are part of a single codebase.


An application built using the microservices architecture is highly scalable and easy to maintain since each service is independently deployed, maintained, and scaled. This approach also makes it modular, which means that changes in a component don’t affect the entire system and errors are localized to the specific service.


In essence, microservices are resilient since services run in isolation and independent of each other, failure of a service doesn’t bring down the system. Unlike the monolithic application where an error in the codebase can bring down the entire application.


Microservices are also technology agnostic, which means each service can be built with a technology stack of choice. This brings a high level of flexibility when building solutions.

Concept of Message Brokers

A message broker is a software that facilitates communication between services. It ensures that the microservices exchange messages and information reliably between each other. It facilitates communication between services even when they are written in different languages or frameworks by providing a standardized means of handling the flow of data.


Message brokers work by managing the exchange of messages between a Producer and a Consumer. This can be implemented by either a Publish-Subscribe (Pub-Sub) or Message Queue model.


In the Pub-Sub system, the producer sends a message to a channel, where subscribed consumers can receive that message. In a Message Queue model, the producer sends messages to a specific queue, where a single consumer consumes it, after consumption, the message is removed from the queue.

Implementing Microservices with Node.js and RabbitMQ

In this section, we are going to be building a simple software implementing the microservice architecture using RabbitMQ and Node.js. We would be building three services: the Product, Order, and Notification services.

Setting Up the Project

First, create a directory for your application, then initialize the project and install dependencies:

mkdir ./path_to_project_directorynpm init -ynpm install express amqplib dotenv

RabbitMQ Setup

You can set up RabbitMQ using Docker locally via an installation file, or by using a managed service like CloudAMPQ.


CloudAMPQ is a fully managed RabbitMQ service, which automates setup, scalability, and operation. CloudAMPQ has various subscription plans including a free plan.

Docker Setup

Let’s pull the RabbitMQ docker image:

docker pull rabbitmq


Now run:

docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3-management


This runs the RabbitMQ server on port 5672 and the management interface on 15672 (default username/password is guest).

Local Installation

Download the RabbitMQ installation file alongside the OS-specific instructions. Run the installer, and follow the installation instructions. Ensure the server is running:

Windows:

rabbitmq-service start

Linux:

systemctl status rabbitmq-server.service

then

sudo service rabbitmq-server restart

MacOS:

brew services start rabbitmq

CloudAMPQ Setup

This is the easiest approach to setting up RabbitMQ, which we will be using in this article. Head over to the CloudAMPQ signup page, and create an account. Create an instance; give it a name, select the free plan, select a region, and complete the setup.


After setting up your RabbitMQ copy your connection URL amqps://username:[email protected]/vhost

Creating the Product Service

The product service manages product creation and publishes the message to the product_queue to be consumed by the Order Service.

Create the product service directory:

mkdir product_service

cd product_service


Create an index.js file.


Copy the following code:

require('dotenv').config(); // Load environment variables

const express = require('express');
const amqp = require('amqplib/callback_api');
const app = express();

app.use(express.json());

let channel;

// Connect to RabbitMQ 
amqp.connect(process.env.RABBITMQ_URL, (err, conn) => {
  if (err) throw err;
  conn.createChannel((err, ch) => {
    if (err) throw err;
    channel = ch;
    channel.assertQueue('product_queue', { durable: false });
  });
});

// Product endpoint
app.post('/product', (req, res) => {
  const product = req.body;

  // Publish product creation event to RabbitMQ
  channel.sendToQueue('product_queue', Buffer.from(JSON.stringify(product)));
  res.status(201).send(`Product created: ${product.name}`);
});

// Use port from environment variable
app.listen(process.env.PRODUCT_SERVICE_PORT, () => {
  console.log(`Product service running on port ${process.env.PRODUCT_SERVICE_PORT}`);
});


Create a .env file and add the connection details:

RABBITMQ_URL= *your connection URL*


Add PRODUCT_SERVICE_PORT=3000 to the .env file.


run node index.js

Creating the Order Service

This listens for product creation event, and processes orders, and publishes the order to the order_queue, which would be consumed by the Notification Service.


Create the order service directory from your project root directory:

mkdir order_service

cd order_service


Create an index.js file.


Write the following code:

require('dotenv').config();

const express = require('express');
const amqp = require('amqplib/callback_api');
const app = express();

app.use(express.json());

let channel;

// Connect to RabbitMQ
amqp.connect(process.env.RABBITMQ_URL, (err, conn) => {
  if (err) throw err;
  conn.createChannel((err, ch) => {
    if (err) throw err;
    channel = ch;
    channel.assertQueue('product_queue', { durable: false });
    // Consume product creation events
    channel.consume('product_queue', (msg) => {
      const product = JSON.parse(msg.content.toString());
      console.log(`Received product: ${product.name}`);
      // Handle product logic (e.g., create order)
    }, { noAck: true });
  });
});

// Order endpoint
app.post('/order', (req, res) => {
  const order = req.body;

  // Publish order event to RabbitMQ
  channel.sendToQueue('order_queue', Buffer.from(JSON.stringify(order)));
  res.status(201).send(`Order created for product: ${order.product}`);
});

// Use port from environment variable
app.listen(process.env.ORDER_SERVICE_PORT, () => {
  console.log(`Order service running on port ${process.env.ORDER_SERVICE_PORT}`);
});


Create a .env file, and add the connection details:

RABBITMQ_URL= *your connection URL*


Add ORDER_SERVICE_PORT=3001 to the .env file.


Run node index.js

Creating the Notification Service

This handle sending notifications when orders are placed.


Create the notification service directory:

mkdir notification_service

cd notification_service


Create an index.js file


Write the following code:

require('dotenv').config();

const express = require('express');
const amqp = require('amqplib/callback_api');
const app = express();

app.use(express.json());

let channel;

// Connect to RabbitMQ
amqp.connect(process.env.RABBITMQ_URL, (err, conn) => {
  if (err) throw err;
  conn.createChannel((err, ch) => {
    if (err) throw err;
    channel = ch;
    channel.assertQueue('product_queue', { durable: false });
    // Consume order events
    channel.consume('order_queue', (msg) => {
      const order = JSON.parse(msg.content.toString());
      console.log(`Notification: Order placed for product ${order.product}`);
      // Handle notification logic (e.g., send email)
    }, { noAck: true });
  });
});

app.listen(process.env.NOTIFICATION_SERVICE_PORT, () => {
  console.log(`Notification service running on port ${process.env.NOTIFICATION_SERVICE_PORT}`);
});


Create a .env file, and add the connection details:

RABBITMQ_URL= *your connection URL*


Add NOTIFICATION_SERVICE_PORT=3002 to the .env file.


run node index.js

Testing the Setup

We are going to be using Postman to test our services.

Product Service

Order Service

Notification Service

Messages are logged to the console based on the events from the order_queue

Conclusion

By implementing the microservice architecture, organizations have been able to build scalable and resilient applications. The development and maintenance process becomes streamlined due to increased modularity, and resources are effectively utilized.


This repo contains the code.


If you have any questions, please, drop them in the comments below.


Cover Image by macrovector on Freepik