The Ultimate Guide to Microservices Architecture using NodeJS

UtkarshUtkarsh
4 min read

Introduction

Microservices: the buzzword that makes engineers look smart and managers panic. If you've ever wanted to break your monolithic Node.js app into smaller, independent services (or just want an excuse to create more GitHub repositories), this guide is for you.

Why Microservices?

Microservices allow you to:

  • Scale individual components independently.

  • Deploy services without affecting the entire application.

  • Increase complexity (just kidding, but also not kidding).

  • Make debugging an exciting scavenger hunt.

Monolithic vs Microservices Architecture

FeatureMonolithic ArchitectureMicroservices Architecture
ScalabilityLimited, scaling means replicating the entire appHighly scalable, only scale the needed service
DeploymentDeploy the entire app togetherDeploy services independently
Technology StackUsually one stack for the whole appEach service can have its own stack
Fault IsolationOne bug can crash the whole appFault in one service doesn't affect others
Inter-Service CommunicationDirect function callsMessage broker or API calls

Project Overview

We'll build a simple e-commerce system with the following microservices:

  1. User Service - Handles user authentication and profiles.

  2. Product Service - Manages product listings.

  3. Order Service - Handles orders and payments.

Each service will be a standalone Express.js app communicating via a message broker (RabbitMQ).

Folder Structure

Before we start coding, let's set up our directory structure like a pro:

microservices-app/
│-- user-service/
│   │-- src/
│   │   │-- controllers/
│   │   │-- routes/
│   │   │-- models/
│   │   │-- services/
│   │   │-- index.js
│   │-- package.json
│
│-- product-service/
│-- order-service/
│-- gateway/
│-- shared/
│   │-- message-broker/
│   │-- utils/
│-- docker-compose.yml
│-- README.md

Each service has its own routes, controllers, and models, while common utilities are kept in the shared/ folder.

Setting Up a Microservice

Let's start by creating our User Service.

Step 1: Initialize the Service

mkdir user-service && cd user-service
npm init -y
npm install express dotenv mongoose cors amqplib

Step 2: Create an Express Server

// user-service/src/index.js
import express from "express";
import dotenv from "dotenv";
dotenv.config();
import userRoutes from "./routes/userRoutes.js";

const app = express();
app.use(express.json());
app.use("/users", userRoutes);

const PORT = process.env.PORT || 5001;
app.listen(PORT, () => console.log(`User Service running on port ${PORT}`));

Step 3: Create User Routes and Controller

// user-service/src/routes/userRoutes.js
import express from "express";
import { registerUser, getUser } from "../controllers/userController.js";
const router = express.Router();

router.post("/register", registerUser);
router.get("/:id", getUser);

export default router;
// user-service/src/controllers/userController.js
export const registerUser = (req, res) => {
    res.json({ message: "User registered successfully!" });
};

export const getUser = (req, res) => {
    res.json({ userId: req.params.id, name: "John Doe" });
};

Boom! We have a working User Service. Now, let’s create the Product and Order Services similarly.

Inter-Service Communication with RabbitMQ

Microservices communicate through a message broker. You can simplify inter-service communication by using REST APIs as well. However, for this tutorial, we’ll use RabbitMQ.

Step 1: Setting Up RabbitMQ

First, install RabbitMQ on your system or run it using Docker:

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

Step 2: Create a Message Broker Utility

// shared/message-broker/index.js
import amqp from "amqplib";

let channel;

export const connectRabbitMQ = async () => {
    try {
        const connection = await amqp.connect("amqp://localhost");
        channel = await connection.createChannel();
        console.log("Connected to RabbitMQ");
    } catch (error) {
        console.error("RabbitMQ Connection Error", error);
    }
};

export const publishMessage = async (queue, message) => {
    if (!channel) return;
    await channel.assertQueue(queue);
    channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)));
};

export const consumeMessage = async (queue, callback) => {
    if (!channel) return;
    await channel.assertQueue(queue);
    channel.consume(queue, (msg) => {
        if (msg !== null) {
            callback(JSON.parse(msg.content.toString()));
            channel.ack(msg);
        }
    });
};

Step 3: Sending Messages from Order Service

// order-service/src/index.js
import express from "express";
import dotenv from "dotenv";
dotenv.config();
import { connectRabbitMQ, publishMessage } from "../../shared/message-broker/index.js";

const app = express();
app.use(express.json());

app.post("/order", async (req, res) => {
    const order = { orderId: Date.now(), userId: req.body.userId };
    await publishMessage("ORDER_CREATED", order);
    res.json({ message: "Order placed successfully" });
});

const PORT = process.env.PORT || 5003;
connectRabbitMQ().then(() => {
    app.listen(PORT, () => console.log(`Order Service running on port ${PORT}`));
});

Step 4: Consuming Messages in User Service

// user-service/src/index.js
import { connectRabbitMQ, consumeMessage } from "../../shared/message-broker/index.js";

connectRabbitMQ().then(() => {
    consumeMessage("ORDER_CREATED", (order) => {
        console.log("Received Order Event:", order);
    });
});

Now, when an order is placed, the User Service listens for the event and processes it asynchronously.

Conclusion

Congratulations! You now have a functioning microservices architecture using RabbitMQ for communication. 🎉 No more REST APIs between services—only pure, asynchronous messaging goodness. Happy coding!

0
Subscribe to my newsletter

Read articles from Utkarsh directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Utkarsh
Utkarsh

I'm a MERN Stack developer and technical writer that loves to share his thoughts in words on latest trends and technologies. For queries and opportunities, I'm available at r.utkarsh.0010@gmail.com