Skip to main content

Learn how to break a monolithic Node.js app into clean, scalable microservices using Express, MongoDB, and Docker


 As your application grows, a single backend often turns into a tangled mess — harder to scale, test, and deploy.



That’s where microservice architecture comes in.

Instead of one large codebase, you split your app into independent, loosely coupled services — each handling one specific business function (like users, products, or orders).

In this blog, we’ll build a simple microservice-based system using:

  • 🟒 Node.js + Express — for APIs

  • πŸƒ MongoDB — as individual service databases

  • 🐳 Docker Compose — to run them together


🧠 What Are Microservices?

Microservices are small, autonomous services that communicate over APIs.
Each service:

  • Has its own database

  • Runs independently

  • Can be scaled or deployed separately

Example:
A shopping app might have:

  • πŸ‘€ User Service → handles signup/login

  • πŸ“¦ Product Service → manages items

  • πŸ›’ Order Service → processes purchases

Instead of one app handling all three, you split them like this:

[User Service][Order Service][Product Service] ↘ ↘ MongoDB MongoDB





⚙️ Step 1 — Project Setup

Let’s create a folder structure:

microservices-demo/ │ ├── user-service/ ├── product-service/ ├── order-service/ └── docker-compose.yml

Each service is its own Express + MongoDB app.


πŸ‘€ Step 2 — Creating the User Service

Inside user-service/index.js:

import express from "express"; import mongoose from "mongoose"; const app = express(); app.use(express.json()); mongoose.connect("mongodb://localhost:27017/users"); const UserSchema = new mongoose.Schema({ name: String, email: String, }); const User = mongoose.model("User", UserSchema); app.post("/users", async (req, res) => { const user = await User.create(req.body); res.json(user); }); app.listen(4001, () => console.log("User Service running on port 4001"));

What’s happening:

  • A small API that creates users in a separate database.

  • Runs on port 4001.


πŸ“¦ Step 3 — Product Service

product-service/index.js:

import express from "express"; import mongoose from "mongoose"; const app = express(); app.use(express.json()); mongoose.connect("mongodb://localhost:27017/products"); const ProductSchema = new mongoose.Schema({ name: String, price: Number, }); const Product = mongoose.model("Product", ProductSchema); app.post("/products", async (req, res) => { const product = await Product.create(req.body); res.json(product); }); app.listen(4002, () => console.log("Product Service running on port 4002"));

Each service has its own database and API — completely independent.


πŸ›’ Step 4 — Order Service & Service Communication

In order-service/index.js:

import express from "express"; import mongoose from "mongoose"; import axios from "axios"; const app = express(); app.use(express.json()); mongoose.connect("mongodb://localhost:27017/orders"); const OrderSchema = new mongoose.Schema({ userId: String, productId: String, status: String, }); const Order = mongoose.model("Order", OrderSchema); app.post("/orders", async (req, res) => { const { userId, productId } = req.body; // Communicate with other services const user = await axios.get(`http://localhost:4001/users/${userId}`); const product = await axios.get(`http://localhost:4002/products/${productId}`); const order = await Order.create({ userId, productId, status: "Created", }); res.json({ message: "Order created successfully", order, user: user.data, product: product.data, }); }); app.listen(4003, () => console.log("Order Service running on port 4003"));

Key concept:
Services communicate via HTTP. Later, you could upgrade this to message queues (RabbitMQ, Kafka) for better scalability.


πŸ”— Step 5 — Adding an API Gateway (Optional but Recommended)

Instead of calling each service directly, create an API Gateway.

gateway/index.js:

import express from "express"; import { createProxyMiddleware } from "http-proxy-middleware"; const app = express(); app.use("/users", createProxyMiddleware({ target: "http://localhost:4001", changeOrigin: true })); app.use("/products", createProxyMiddleware({ target: "http://localhost:4002", changeOrigin: true })); app.use("/orders", createProxyMiddleware({ target: "http://localhost:4003", changeOrigin: true })); app.listen(4000, () => console.log("API Gateway running on port 4000"));

Now, your frontend can just call:

http://localhost:4000/users http://localhost:4000/products http://localhost:4000/orders

🐳 Step 6 — Dockerize Everything

Create a Dockerfile for each service:

FROM node:18 WORKDIR /app COPY package*.json ./ RUN npm install COPY . . CMD ["npm", "start"]

Then a docker-compose.yml at the root:

version: "3" services: user-service: build: ./user-service ports: - "4001:4001" product-service: build: ./product-service ports: - "4002:4002" order-service: build: ./order-service ports: - "4003:4003" gateway: build: ./gateway ports: - "4000:4000"

Run it all with:

docker compose up --build

πŸ”’ Step 7 — Authentication & Security (Next Steps)

To make this production-ready:

  • Add JWT authentication via a dedicated Auth service.

  • Use service-to-service tokens for internal requests.

  • Secure your databases and API Gateway.


πŸ“ˆ Step 8 — Testing the System

Use Postman or Thunder Client:

  1. Create a user → POST /users

  2. Create a product → POST /products

  3. Create an order → POST /orders (include userId + productId)

  4. Verify responses and linked data

You’ll see independent services working together like a small distributed system.


🧭 Conclusion

Congratulations — you just built a microservice architecture using Node.js and MongoDB! πŸŽ‰

You learned:

  • How to separate services by domain

  • Communicate using REST APIs

  • Deploy with Docker Compose

  • Use an API Gateway for unified routing

πŸ’‘ Tip: For larger projects, explore message queues (RabbitMQ, Kafka), Kubernetes, and service discovery for even more scalable setups.


✍️ Final Thoughts

Microservices aren’t always necessary — for small apps, monoliths are faster to build.
But if you’re planning for growth, scalability, or team collaboration, microservices give you that flexibility.

“Build small, deploy often, scale independently.”


Comments