Post 15 of 15 | Final Post
What to Learn Next — Testing, Advanced Patterns, Kubernetes, and the Road Ahead
You have completed the core Moleculer course. You can build a full microservices backend, containerize it, add fault tolerance, caching, authentication, observability, and deploy it with Docker Compose. That puts you ahead of most Node.js developers.
This final post covers what comes after. The topics here are not small additions. Each one is a significant skill area. Think of this post as your roadmap for the next six to twelve months.
Topic 1: Testing Moleculer Services
Testing is the first thing you should learn after completing this course. Untested microservices are dangerous because failures in one service cascade into others in unexpected ways.
Moleculer has excellent testing support built in.
Unit Testing Actions
For unit testing individual actions, you do not need a running broker. You test the handler function in isolation.
Install Jest:
npm install --save-dev jest
Create a test file:
tests/unit/user.service.test.js
"use strict";
const { ServiceBroker } = require("moleculer");
const UserService = require("../../services/user.service");
describe("User Service", () => {
let broker;
// Create a broker before all tests
beforeAll(async () => {
broker = new ServiceBroker({ logger: false });
broker.createService(UserService);
await broker.start();
});
// Stop broker after all tests
afterAll(async () => {
await broker.stop();
});
describe("user.login action", () => {
it("should return a token on valid credentials", async () => {
const result = await broker.call("user.login", {
email: "rahul@example.com",
password: "password123"
});
expect(result).toHaveProperty("message", "Login successful");
expect(result).toHaveProperty("user");
expect(result.user).not.toHaveProperty("password");
});
it("should throw error on invalid credentials", async () => {
await expect(
broker.call("user.login", {
email: "wrong@example.com",
password: "wrongpassword"
})
).rejects.toThrow("Invalid email or password");
});
it("should throw validation error when email is missing", async () => {
await expect(
broker.call("user.login", {
password: "password123"
})
).rejects.toThrow();
});
});
});
Integration Testing — Multiple Services
Integration tests test how services communicate with each other:
"use strict";
const { ServiceBroker } = require("moleculer");
const UserService = require("../../services/user.service");
const OrderService = require("../../services/order.service");
const ProductService = require("../../services/product.service");
describe("Order Flow Integration", () => {
let broker;
beforeAll(async () => {
broker = new ServiceBroker({ logger: false });
// Load all services into one broker for integration testing
broker.createService(UserService);
broker.createService(OrderService);
broker.createService(ProductService);
await broker.start();
});
afterAll(async () => {
await broker.stop();
});
it("should create an order and reduce product stock", async () => {
// Step 1: Create a product
const product = await broker.call("product.create", {
name: "Test Keyboard",
price: 1000,
stock: 10
});
// Step 2: Create an order
const order = await broker.call("order.create", {
productId: product._id.toString(),
quantity: 2
}, {
meta: { user: { id: "user-1", role: "user" } }
});
expect(order).toHaveProperty("status", "confirmed");
expect(order).toHaveProperty("totalPrice", 2000);
// Step 3: Verify stock reduced
const updatedProduct = await broker.call("product.get", {
id: product._id.toString()
});
expect(updatedProduct.stock).toBe(8);
});
});
Mocking Services in Tests
When you want to test one service in isolation without its dependencies, mock the dependency:
describe("Order Service with mocked User Service", () => {
let broker;
beforeAll(async () => {
broker = new ServiceBroker({ logger: false });
// Mock the user service — return fake data
broker.createService({
name: "user",
actions: {
get() {
return { id: "1", name: "Test User", email: "test@test.com" };
}
}
});
broker.createService(OrderService);
await broker.start();
});
afterAll(() => broker.stop());
it("should create order using mocked user", async () => {
const order = await broker.call("order.create", {
productId: "product-1",
quantity: 1
}, {
meta: { user: { id: "1" } }
});
expect(order).toBeDefined();
});
});
Add test script to package.json:
"scripts": {
"test": "jest --testEnvironment=node",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
Topic 2: The Saga Pattern — Distributed Transactions
This is one of the most important advanced topics in microservices. Understand the problem first.
In a monolith, if you create an order and charge a credit card in the same function, and the charge fails, you can roll back the entire database transaction. Both operations either succeed or both fail. This is called an ACID transaction.
In microservices, the order is saved in the order service database and the payment is charged in the payment service database. They are separate processes, separate databases. If the payment fails after the order was saved, there is no global rollback. You now have an order in the database with no corresponding payment. Data is inconsistent.
The Saga pattern solves this. A saga is a sequence of local transactions. If any step fails, the saga runs compensating transactions to undo the previous steps.
There are two types of sagas:
Choreography-based Saga — Services communicate through events. No central coordinator. Each service knows what to do when it receives an event and what to do if something goes wrong.
order.service → emits "order.created"
payment.service → listens, charges card, emits "payment.completed" or "payment.failed"
order.service → listens to "payment.completed" → updates order to "confirmed"
order.service → listens to "payment.failed" → updates order to "cancelled"
inventory.service → listens to "order.confirmed" → reduces stock
Orchestration-based Saga — A central saga orchestrator calls each step and handles failures:
// saga/order.saga.js
module.exports = {
name: "order-saga",
actions: {
async start(ctx) {
const { userId, productId, quantity } = ctx.params;
let orderId = null;
try {
// Step 1: Create order
const order = await ctx.call("order.create", {
userId, productId, quantity
});
orderId = order._id;
// Step 2: Charge payment
await ctx.call("payment.charge", {
userId,
amount: order.totalPrice,
orderId
});
// Step 3: Reduce stock
await ctx.call("product.reduceStock", {
id: productId,
quantity
});
// All steps succeeded
await ctx.call("order.updateStatus", {
id: orderId,
status: "confirmed"
});
return { success: true, orderId };
} catch (err) {
this.logger.error("Saga failed, running compensation", err);
// Compensating transaction — undo what was done
if (orderId) {
await ctx.call("order.updateStatus", {
id: orderId,
status: "cancelled"
});
// If payment was charged, refund it
await ctx.call("payment.refund", {
orderId
}).catch(e => this.logger.error("Refund failed", e));
}
throw err;
}
}
}
};
Moleculer has an official package called moleculer-workflows that provides a more structured saga implementation. Look it up at github.com/moleculerjs/moleculer-workflows when you are ready for this.
Topic 3: Advanced moleculer-db — Populates and Hooks
You used basic moleculer-db in Post 11. Two features you will need in real projects:
Populates — Joining Data Across Services
Instead of manually calling user service from order service to get user details, moleculer-db can auto-populate related data:
module.exports = {
name: "order",
mixins: [DbMixin],
model: OrderModel,
settings: {
populates: {
// When listing orders, automatically fetch user details
user: {
action: "user.get",
params: {
fields: ["name", "email"]
}
},
// Automatically fetch product details
product: {
action: "product.get",
params: {
fields: ["name", "price"]
}
}
}
},
actions: {
list: {
rest: { method: "GET", path: "/" },
params: {
// Client can request population
populate: { type: "array", items: "string", optional: true }
}
}
}
};
Call with populate:
GET /api/order?populate[]=user&populate[]=product
Response:
{
"rows": [
{
"_id": "order-1",
"userId": "user-1",
"user": { "name": "Rahul Sharma", "email": "rahul@example.com" },
"productId": "product-1",
"product": { "name": "Mechanical Keyboard", "price": 2999 },
"quantity": 2,
"totalPrice": 5998
}
]
}
Entity Hooks — Before and After Database Operations
module.exports = {
name: "product",
mixins: [DbMixin],
hooks: {
before: {
// Runs before every create action
create(ctx) {
// Normalize data before saving
ctx.params.name = ctx.params.name.trim();
ctx.params.slug = ctx.params.name.toLowerCase().replace(/\s+/g, "-");
}
},
after: {
// Runs after every get action
get(ctx, res) {
// Transform data before returning
if (res) {
res.priceFormatted = `Rs. ${res.price.toLocaleString()}`;
}
return res;
}
}
}
};
Topic 4: Moleculer Middlewares
Middlewares in Moleculer are different from Express middlewares. They intercept action calls, event emissions, and broker lifecycle events globally. They are the correct way to add cross-cutting behavior like logging every action call, adding rate limiting, or transforming responses globally.
// middlewares/logger.middleware.js
module.exports = {
name: "ActionLogger",
// Wraps every action call
localAction(next, action) {
return async function(ctx) {
const start = Date.now();
try {
const result = await next(ctx);
const duration = Date.now() - start;
ctx.broker.logger.info(`Action ${action.name} completed in ${duration}ms`);
return result;
} catch (err) {
const duration = Date.now() - start;
ctx.broker.logger.error(`Action ${action.name} failed after ${duration}ms`, err.message);
throw err;
}
};
}
};
Register in moleculer.config.js:
const ActionLogger = require("./middlewares/logger.middleware");
module.exports = {
middlewares: [ActionLogger]
};
Now every action call in every service is automatically logged with duration. Zero changes to service code.
Topic 5: Kubernetes — Beyond Docker Compose
Docker Compose is excellent for development and small production deployments. For large scale production, Kubernetes is the standard.
Kubernetes gives you:
- Automatic restart of crashed containers
- Rolling deployments with zero downtime
- Auto-scaling based on CPU or request count
- Load balancing across pods
- Health checks and readiness probes
- Secrets management
A basic Kubernetes deployment for your user service:
k8s/user-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: user-service
spec:
replicas: 3 # Run 3 instances
selector:
matchLabels:
app: user-service
template:
metadata:
labels:
app: user-service
spec:
containers:
- name: user-service
image: your-registry/ecommerce:latest
env:
- name: SERVICES
value: "user"
- name: MONGO_URI
valueFrom:
secretKeyRef:
name: app-secrets
key: mongo-uri
- name: REDIS_URI
valueFrom:
secretKeyRef:
name: app-secrets
key: redis-uri
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "500m"
livenessProbe:
httpGet:
path: /health
port: 3000
initialDelaySeconds: 30
periodSeconds: 10
The path from Docker Compose to Kubernetes is well documented. When you are ready, look at:
- Minikube for running Kubernetes locally
- Helm for packaging Kubernetes applications
- Google Kubernetes Engine or AWS EKS for managed Kubernetes in the cloud
Topic 6: Additional Official Moleculer Packages
There are many official and community packages that extend Moleculer. These are the most useful ones for real projects:
moleculer-mail — Send emails via various providers (Mailgun, SendGrid, SMTP):
npm install moleculer-mail
moleculer-bull — Background job queues using Bull and Redis. For long-running tasks that should not block the request:
npm install moleculer-bull
moleculer-cron — Scheduled jobs using cron expressions:
npm install moleculer-cron
moleculer-io — Socket.IO integration for real-time features:
npm install moleculer-io
moleculer-apollo-server — GraphQL support alongside REST:
npm install moleculer-apollo-server
moleculer-telegram — Send Telegram messages from services. Useful for production alerts:
npm install moleculer-telegram
Topic 7: TypeScript with Moleculer
Moleculer has first-class TypeScript support. If your team uses TypeScript, you can write strongly typed services:
npm install typescript @types/node
npm install --save-dev ts-node
A TypeScript service looks like this:
import { Service, ServiceSchema, Context } from "moleculer";
interface UserCreateParams {
name: string;
email: string;
password: string;
}
interface UserResponse {
id: string;
name: string;
email: string;
}
const UserService: ServiceSchema = {
name: "user",
actions: {
create: {
params: {
name: "string",
email: "email",
password: { type: "string", min: 6 }
},
async handler(ctx: Context<UserCreateParams>): Promise<UserResponse> {
const { name, email, password } = ctx.params;
// TypeScript now knows exactly what fields are available
return { id: "1", name, email };
}
}
}
};
export default UserService;
TypeScript catches errors at compile time instead of runtime. For large teams and long-lived projects, this is extremely valuable.
Where to Find Help
When you are stuck, these are the best places to get answers:
Official Documentation
moleculer.services/docs/0.15 — Always read the official docs first. They are well written and kept up to date.
GitHub Repository
github.com/moleculerjs/moleculer — Read the source code, check existing issues, open new issues.
Discord Community
discord.gg/TSEcDRP — The Moleculer Discord has an active community. The maintainers themselves answer questions there.
Stack Overflow
Search for questions tagged with moleculer. Many common problems are already answered.
GitHub Discussions
github.com/moleculerjs/moleculer/discussions — For longer questions and architectural discussions.
Recommended Learning Path After This Course
Here is a structured path for the next six to twelve months based on what you have learned:
Month 1 and 2: Solidify what you learned
- Add tests to the project you built in this course
- Add the saga pattern to your order flow
- Deploy your project to a real server using Docker Compose
- Get comfortable with Jaeger traces and Prometheus metrics
Month 3 and 4: Go deeper into the ecosystem
- Learn moleculer-bull for background jobs
- Add GraphQL support with moleculer-apollo-server
- Add WebSocket support with moleculer-io
- Practice the populates feature of moleculer-db
Month 5 and 6: Production skills
- Learn Kubernetes basics with Minikube
- Convert your Docker Compose deployment to Kubernetes
- Set up a proper CI/CD pipeline with GitHub Actions
- Learn about service mesh concepts with Istio
Month 7 and beyond: Architecture
- Study the twelve-factor app methodology at 12factor.net
- Read about domain-driven design — it helps you decide how to split services
- Study event sourcing and CQRS patterns
- Look at other microservices frameworks to understand tradeoffs — NestJS microservices, Seneca
Final Thoughts
When you started this course you knew MERN and MEAN. You could build a monolith. You did not know how to think in services or how to handle the complexity that comes with distributed systems.
Now you understand:
- Why microservices exist and when they are the right choice
- How to design services with clear boundaries
- How to make services communicate reliably
- How to protect your system when things fail
- How to observe what is happening inside your running application
- How to package and deploy a complete microservices system
One important thing to remember as you go forward: microservices are not always the right choice. For a small team, a small application, or an early startup, a well-structured monolith is often better. Start with a monolith, identify the boundaries, and extract services when you have a real reason — a team needs to work independently, a specific component needs to scale differently, or a part needs a different technology.
Moleculer is powerful but it is a tool. Use it when it solves a real problem. The knowledge you have gained about distributed systems, fault tolerance, event-driven architecture, and observability applies far beyond Moleculer. These concepts work in any language, any framework, any cloud.
You are now equipped to build production-grade distributed systems with Node.js. That is a significant achievement.
Complete Course Summary — All 15 Posts
Phase 1 — Foundation
Post 1: What is microservices and why Moleculer
Post 2: Installation and first working service
Phase 2 — Core Concepts
Post 3: ServiceBroker and moleculer.config.js
Post 4: Services, Actions, validation, error handling
Post 5: Events, emit vs broadcast, wildcard listeners
Phase 3 — Communication
Post 6: Context object, requestID, meta, call chain
Post 7: Transporters, TCP, NATS, Redis, service discovery
Phase 4 — Fault Tolerance
Post 8: Timeout, Retry, Circuit Breaker, Bulkhead, Fallback
Phase 5 — Caching
Post 9: Memory and Redis caching, TTL, cache invalidation
Phase 6 — API Gateway
Post 10: moleculer-web, authentication, authorization, JWT
Phase 7 — Database
Post 11: moleculer-db with MongoDB and Mongoose
Phase 8 — Observability
Post 12: Logging, Prometheus metrics, Jaeger tracing
Phase 9 — Deployment
Post 13: Docker and Docker Compose
Post 14: Complete project review and best practices
Post 15: Testing, Sagas, Kubernetes, what to learn next
Course Progress: 15 of 15 posts complete.
You have completed the Moleculer Microservices Course.
No comments:
Post a Comment