Learn microservices architecture, service communication, API gateway patterns, and container orchestration with Node.js.
Learn microservices architecture, service communication, API gateway patterns, and container orchestration with Node.js.
Understand microservices architecture principles, benefits, and implementation patterns
Content by: Parth Patel
Node.js Developer
Microservices is an architectural style where an application is built as a collection of small, independent services that communicate over well-defined APIs. Each service is responsible for a specific business capability and can be developed, deployed, and scaled independently.
// User Service (users-service/index.js)
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const app = express();
app.use(cors());
app.use(express.json());
// Connect to User Service Database
mongoose.connect('mongodb://localhost:27017/users-service');
// User Schema
const userSchema = new mongoose.Schema({
username: String,
email: String,
password: String,
createdAt: { type: Date, default: Date.now }
});
const User = mongoose.model('User', userSchema);
// User Service Routes
app.get('/api/users', async (req, res) => {
try {
const users = await User.find({}, '-password');
res.json(users);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch users' });
}
});
app.get('/api/users/:id', async (req, res) => {
try {
const user = await User.findById(req.params.id, '-password');
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
res.json(user);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch user' });
}
});
app.post('/api/users', async (req, res) => {
try {
const user = new User(req.body);
await user.save();
res.status(201).json({ id: user._id, username: user.username, email: user.email });
} catch (error) {
res.status(400).json({ error: 'Failed to create user' });
}
});
const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
console.log(`User service running on port ${PORT}`);
});
// Order Service (orders-service/index.js)
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const axios = require('axios');
const app = express();
app.use(cors());
app.use(express.json());
// Connect to Order Service Database
mongoose.connect('mongodb://localhost:27017/orders-service');
// Order Schema
const orderSchema = new mongoose.Schema({
userId: String,
products: [{
productId: String,
quantity: Number,
price: Number
}],
total: Number,
status: { type: String, default: 'pending' },
createdAt: { type: Date, default: Date.now }
});
const Order = mongoose.model('Order', orderSchema);
// Order Service Routes
app.get('/api/orders', async (req, res) => {
try {
const orders = await Order.find();
res.json(orders);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch orders' });
}
});
app.post('/api/orders', async (req, res) => {
try {
const { userId, products } = req.body;
// Verify user exists (inter-service communication)
try {
await axios.get(`http://localhost:3001/api/users/${userId}`);
} catch (error) {
return res.status(400).json({ error: 'User not found' });
}
const order = new Order({
userId,
products,
total: products.reduce((sum, product) => sum + (product.price * product.quantity), 0)
});
await order.save();
res.status(201).json(order);
} catch (error) {
res.status(400).json({ error: 'Failed to create order' });
}
});
const PORT = process.env.PORT || 3002;
app.listen(PORT, () => {
console.log(`Order service running on port ${PORT}`);
});
Test your understanding of this topic:
Learn different patterns for inter-service communication including HTTP, message queues, and event-driven architecture
Content by: Bansi Patel
Node.js Developer
Microservices need to communicate with each other to fulfill business requirements. There are several patterns for inter-service communication, each with its own trade-offs.
// Service-to-Service HTTP Communication
const express = require('express');
const axios = require('axios');
const app = express();
// Service discovery configuration
const services = {
users: 'http://localhost:3001',
orders: 'http://localhost:3002',
products: 'http://localhost:3003',
payments: 'http://localhost:3004'
};
// Circuit breaker implementation
class CircuitBreaker {
constructor(failureThreshold = 5, timeout = 60000) {
this.failureThreshold = failureThreshold;
this.timeout = timeout;
this.failures = 0;
this.lastFailureTime = null;
this.state = 'CLOSED'; // CLOSED, OPEN, HALF_OPEN
}
async call(serviceCall) {
if (this.state === 'OPEN') {
if (Date.now() - this.lastFailureTime > this.timeout) {
this.state = 'HALF_OPEN';
} else {
throw new Error('Circuit breaker is OPEN');
}
}
try {
const result = await serviceCall();
this.onSuccess();
return result;
} catch (error) {
this.onFailure();
throw error;
}
}
onSuccess() {
this.failures = 0;
this.state = 'CLOSED';
}
onFailure() {
this.failures++;
this.lastFailureTime = Date.now();
if (this.failures >= this.failureThreshold) {
this.state = 'OPEN';
}
}
}
// Create circuit breakers for each service
const circuitBreakers = {
users: new CircuitBreaker(),
orders: new CircuitBreaker(),
products: new CircuitBreaker(),
payments: new CircuitBreaker()
};
// Service communication with circuit breaker
async function callService(serviceName, endpoint, options = {}) {
const circuitBreaker = circuitBreakers[serviceName];
const serviceUrl = services[serviceName];
return circuitBreaker.call(async () => {
const response = await axios({
method: options.method || 'GET',
url: `${serviceUrl}${endpoint}`,
data: options.data,
headers: options.headers,
timeout: 5000
});
return response.data;
});
}
// API Gateway routes
app.get('/api/orders/:orderId/details', async (req, res) => {
try {
const { orderId } = req.params;
// Get order details
const order = await callService('orders', `/api/orders/${orderId}`);
// Get user details
const user = await callService('users', `/api/users/${order.userId}`);
// Get product details for each product in the order
const productPromises = order.products.map(product =>
callService('products', `/api/products/${product.productId}`)
);
const products = await Promise.all(productPromises);
// Combine the data
const orderDetails = {
...order,
user,
products: order.products.map((orderProduct, index) => ({
...orderProduct,
details: products[index]
}))
};
res.json(orderDetails);
} catch (error) {
console.error('Error fetching order details:', error);
res.status(500).json({ error: 'Failed to fetch order details' });
}
});
// Health check endpoint
app.get('/health', (req, res) => {
res.json({ status: 'healthy', timestamp: new Date().toISOString() });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`API Gateway running on port ${PORT}`);
});
// Message Queue with RabbitMQ
const amqp = require('amqplib');
const express = require('express');
const app = express();
app.use(express.json());
// RabbitMQ connection
let channel;
async function connectToRabbitMQ() {
try {
const connection = await amqp.connect('amqp://localhost');
channel = await connection.createChannel();
// Declare queues
await channel.assertQueue('order.created', { durable: true });
await channel.assertQueue('order.processed', { durable: true });
await channel.assertQueue('payment.processed', { durable: true });
await channel.assertQueue('inventory.updated', { durable: true });
console.log('Connected to RabbitMQ');
} catch (error) {
console.error('Failed to connect to RabbitMQ:', error);
}
}
// Publish message to queue
async function publishMessage(queue, message) {
if (!channel) {
throw new Error('RabbitMQ channel not available');
}
return channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)), {
persistent: true
});
}
// Consume messages from queue
async function consumeMessages(queue, callback) {
if (!channel) {
throw new Error('RabbitMQ channel not available');
}
await channel.consume(queue, async (msg) => {
if (msg) {
try {
const data = JSON.parse(msg.content.toString());
await callback(data);
channel.ack(msg);
} catch (error) {
console.error('Error processing message:', error);
channel.nack(msg);
}
}
});
}
// Order Service - Publish order created event
app.post('/api/orders', async (req, res) => {
try {
const order = new Order(req.body);
await order.save();
// Publish order created event
await publishMessage('order.created', {
orderId: order._id,
userId: order.userId,
products: order.products,
total: order.total,
timestamp: new Date().toISOString()
});
res.status(201).json(order);
} catch (error) {
res.status(400).json({ error: 'Failed to create order' });
}
});
// Payment Service - Consume order created events
consumeMessages('order.created', async (data) => {
console.log('Processing payment for order:', data.orderId);
// Process payment logic here
const paymentResult = await processPayment(data);
// Publish payment processed event
await publishMessage('payment.processed', {
orderId: data.orderId,
paymentId: paymentResult.paymentId,
status: paymentResult.status,
timestamp: new Date().toISOString()
});
});
// Inventory Service - Consume payment processed events
consumeMessages('payment.processed', async (data) => {
if (data.status === 'completed') {
console.log('Updating inventory for order:', data.orderId);
// Update inventory logic here
await updateInventory(data.orderId);
// Publish inventory updated event
await publishMessage('inventory.updated', {
orderId: data.orderId,
status: 'updated',
timestamp: new Date().toISOString()
});
}
});
// Order Service - Consume inventory updated events
consumeMessages('inventory.updated', async (data) => {
console.log('Marking order as processed:', data.orderId);
// Update order status
await Order.findByIdAndUpdate(data.orderId, { status: 'processed' });
// Publish order processed event
await publishMessage('order.processed', {
orderId: data.orderId,
status: 'processed',
timestamp: new Date().toISOString()
});
});
// Initialize RabbitMQ connection
connectToRabbitMQ();
const PORT = process.env.PORT || 3002;
app.listen(PORT, () => {
console.log(`Order service running on port ${PORT}`);
});
Test your understanding of this topic:
Implement API Gateway pattern for routing, authentication, rate limiting, and service aggregation
Content by: Mahin Prajapati
Node.js Developer
An API Gateway acts as a single entry point for all client requests. It handles cross-cutting concerns like authentication, rate limiting, logging, and request routing to appropriate microservices.
// API Gateway with Express and Node.js
const express = require('express');
const rateLimit = require('express-rate-limit');
const helmet = require('helmet');
const cors = require('cors');
const jwt = require('jsonwebtoken');
const axios = require('axios');
const app = express();
// Security middleware
app.use(helmet());
app.use(cors());
app.use(express.json());
// Rate limiting
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // limit each IP to 100 requests per windowMs
message: 'Too many requests from this IP'
});
app.use(limiter);
// Service discovery
const services = {
users: 'http://localhost:3001',
orders: 'http://localhost:3002',
products: 'http://localhost:3003',
payments: 'http://localhost:3004'
};
// Authentication middleware
const authenticateToken = (req, res, next) => {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
jwt.verify(token, 'your-secret-key', (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' });
}
req.user = user;
next();
});
};
// Request logging middleware
const logRequest = (req, res, next) => {
console.log(`${new Date().toISOString()} - ${req.method} ${req.path} - IP: ${req.ip}`);
next();
};
app.use(logRequest);
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
services: Object.keys(services)
});
});
// User service routes
app.get('/api/users', authenticateToken, async (req, res) => {
try {
const response = await axios.get(`${services.users}/api/users`, {
headers: { 'Authorization': req.headers.authorization }
});
res.json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({
error: 'Failed to fetch users'
});
}
});
app.get('/api/users/:id', authenticateToken, async (req, res) => {
try {
const response = await axios.get(`${services.users}/api/users/${req.params.id}`, {
headers: { 'Authorization': req.headers.authorization }
});
res.json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({
error: 'Failed to fetch user'
});
}
});
// Order service routes
app.get('/api/orders', authenticateToken, async (req, res) => {
try {
const response = await axios.get(`${services.orders}/api/orders`, {
headers: { 'Authorization': req.headers.authorization }
});
res.json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({
error: 'Failed to fetch orders'
});
}
});
// Aggregated endpoint - Get order with user and product details
app.get('/api/orders/:orderId/details', authenticateToken, async (req, res) => {
try {
const { orderId } = req.params;
// Get order details
const orderResponse = await axios.get(`${services.orders}/api/orders/${orderId}`, {
headers: { 'Authorization': req.headers.authorization }
});
const order = orderResponse.data;
// Get user details
const userResponse = await axios.get(`${services.users}/api/users/${order.userId}`, {
headers: { 'Authorization': req.headers.authorization }
});
const user = userResponse.data;
// Get product details for each product
const productPromises = order.products.map(product =>
axios.get(`${services.products}/api/products/${product.productId}`, {
headers: { 'Authorization': req.headers.authorization }
})
);
const productResponses = await Promise.all(productPromises);
const products = productResponses.map(response => response.data);
// Combine the data
const orderDetails = {
...order,
user,
products: order.products.map((orderProduct, index) => ({
...orderProduct,
details: products[index]
}))
};
res.json(orderDetails);
} catch (error) {
console.error('Error fetching order details:', error);
res.status(500).json({ error: 'Failed to fetch order details' });
}
});
// Product service routes
app.get('/api/products', async (req, res) => {
try {
const response = await axios.get(`${services.products}/api/products`);
res.json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({
error: 'Failed to fetch products'
});
}
});
// Payment service routes
app.post('/api/payments', authenticateToken, async (req, res) => {
try {
const response = await axios.post(`${services.payments}/api/payments`, req.body, {
headers: { 'Authorization': req.headers.authorization }
});
res.json(response.data);
} catch (error) {
res.status(error.response?.status || 500).json({
error: 'Failed to process payment'
});
}
});
// Error handling middleware
app.use((error, req, res, next) => {
console.error('API Gateway Error:', error);
res.status(500).json({ error: 'Internal server error' });
});
// 404 handler
app.use('*', (req, res) => {
res.status(404).json({ error: 'Route not found' });
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`API Gateway running on port ${PORT}`);
});
Test your understanding of this topic:
Learn container orchestration with Docker Compose and Kubernetes for microservices deployment
Content by: Rajan Butani
Node.js Developer
Docker Compose allows you to define and run multi-container applications. It's perfect for local development and testing of microservices architecture.
# docker-compose.yml
version: '3.8'
services:
# API Gateway
api-gateway:
build: ./api-gateway
ports:
- "3000:3000"
environment:
- NODE_ENV=development
- USERS_SERVICE_URL=http://users-service:3001
- ORDERS_SERVICE_URL=http://orders-service:3002
- PRODUCTS_SERVICE_URL=http://products-service:3003
- PAYMENTS_SERVICE_URL=http://payments-service:3004
depends_on:
- users-service
- orders-service
- products-service
- payments-service
networks:
- microservices-network
# Users Service
users-service:
build: ./users-service
ports:
- "3001:3001"
environment:
- NODE_ENV=development
- MONGODB_URI=mongodb://users-db:27017/users
- JWT_SECRET=your-secret-key
depends_on:
- users-db
networks:
- microservices-network
# Orders Service
orders-service:
build: ./orders-service
ports:
- "3002:3002"
environment:
- NODE_ENV=development
- MONGODB_URI=mongodb://orders-db:27017/orders
- USERS_SERVICE_URL=http://users-service:3001
- RABBITMQ_URL=amqp://rabbitmq:5672
depends_on:
- orders-db
- rabbitmq
networks:
- microservices-network
# Products Service
products-service:
build: ./products-service
ports:
- "3003:3003"
environment:
- NODE_ENV=development
- MONGODB_URI=mongodb://products-db:27017/products
depends_on:
- products-db
networks:
- microservices-network
# Payments Service
payments-service:
build: ./payments-service
ports:
- "3004:3004"
environment:
- NODE_ENV=development
- MONGODB_URI=mongodb://payments-db:27017/payments
- STRIPE_SECRET_KEY=your-stripe-secret-key
depends_on:
- payments-db
networks:
- microservices-network
# Databases
users-db:
image: mongo:5.0
ports:
- "27017:27017"
volumes:
- users-data:/data/db
networks:
- microservices-network
orders-db:
image: mongo:5.0
ports:
- "27018:27017"
volumes:
- orders-data:/data/db
networks:
- microservices-network
products-db:
image: mongo:5.0
ports:
- "27019:27017"
volumes:
- products-data:/data/db
networks:
- microservices-network
payments-db:
image: mongo:5.0
ports:
- "27020:27017"
volumes:
- payments-data:/data/db
networks:
- microservices-network
# Message Queue
rabbitmq:
image: rabbitmq:3-management
ports:
- "5672:5672"
- "15672:15672"
environment:
- RABBITMQ_DEFAULT_USER=admin
- RABBITMQ_DEFAULT_PASS=admin
volumes:
- rabbitmq-data:/var/lib/rabbitmq
networks:
- microservices-network
# Monitoring
prometheus:
image: prom/prometheus
ports:
- "9090:9090"
volumes:
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml
networks:
- microservices-network
grafana:
image: grafana/grafana
ports:
- "3001:3000"
environment:
- GF_SECURITY_ADMIN_PASSWORD=admin
volumes:
- grafana-data:/var/lib/grafana
networks:
- microservices-network
volumes:
users-data:
orders-data:
products-data:
payments-data:
rabbitmq-data:
grafana-data:
networks:
microservices-network:
driver: bridge
# Dockerfile for each service
# users-service/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3001
CMD ["npm", "start"]
# orders-service/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3002
CMD ["npm", "start"]
# products-service/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3003
CMD ["npm", "start"]
# payments-service/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3004
CMD ["npm", "start"]
# api-gateway/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
EXPOSE 3000
CMD ["npm", "start"]
# Kubernetes manifests for microservices
# namespace.yaml
apiVersion: v1
kind: Namespace
metadata:
name: microservices
---
# configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: microservices-config
namespace: microservices
data:
NODE_ENV: "production"
JWT_SECRET: "your-secret-key"
STRIPE_SECRET_KEY: "your-stripe-secret-key"
---
# secret.yaml
apiVersion: v1
kind: Secret
metadata:
name: microservices-secrets
namespace: microservices
type: Opaque
data:
mongodb-uri: bW9uZ29kYjovL21vbmdvZGItc2VydmljZToyNzAxNy8= # base64 encoded
rabbitmq-url: YW1xcDovL3JhYmJpdG1xOjU2NzI= # base64 encoded
---
# users-service-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: users-service
namespace: microservices
spec:
replicas: 3
selector:
matchLabels:
app: users-service
template:
metadata:
labels:
app: users-service
spec:
containers:
- name: users-service
image: your-registry/users-service:latest
ports:
- containerPort: 3001
env:
- name: NODE_ENV
valueFrom:
configMapKeyRef:
name: microservices-config
key: NODE_ENV
- name: JWT_SECRET
valueFrom:
configMapKeyRef:
name: microservices-config
key: JWT_SECRET
- name: MONGODB_URI
valueFrom:
secretKeyRef:
name: microservices-secrets
key: mongodb-uri
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
livenessProbe:
httpGet:
path: /health
port: 3001
initialDelaySeconds: 30
periodSeconds: 10
readinessProbe:
httpGet:
path: /health
port: 3001
initialDelaySeconds: 5
periodSeconds: 5
---
# users-service-service.yaml
apiVersion: v1
kind: Service
metadata:
name: users-service
namespace: microservices
spec:
selector:
app: users-service
ports:
- protocol: TCP
port: 3001
targetPort: 3001
type: ClusterIP
---
# orders-service-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-service
namespace: microservices
spec:
replicas: 3
selector:
matchLabels:
app: orders-service
template:
metadata:
labels:
app: orders-service
spec:
containers:
- name: orders-service
image: your-registry/orders-service:latest
ports:
- containerPort: 3002
env:
- name: NODE_ENV
valueFrom:
configMapKeyRef:
name: microservices-config
key: NODE_ENV
- name: MONGODB_URI
valueFrom:
secretKeyRef:
name: microservices-secrets
key: mongodb-uri
- name: RABBITMQ_URL
valueFrom:
secretKeyRef:
name: microservices-secrets
key: rabbitmq-url
resources:
requests:
memory: "128Mi"
cpu: "100m"
limits:
memory: "256Mi"
cpu: "200m"
---
# api-gateway-deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-gateway
namespace: microservices
spec:
replicas: 2
selector:
matchLabels:
app: api-gateway
template:
metadata:
labels:
app: api-gateway
spec:
containers:
- name: api-gateway
image: your-registry/api-gateway:latest
ports:
- containerPort: 3000
env:
- name: NODE_ENV
valueFrom:
configMapKeyRef:
name: microservices-config
key: NODE_ENV
resources:
requests:
memory: "256Mi"
cpu: "200m"
limits:
memory: "512Mi"
cpu: "500m"
---
# api-gateway-service.yaml
apiVersion: v1
kind: Service
metadata:
name: api-gateway
namespace: microservices
spec:
selector:
app: api-gateway
ports:
- protocol: TCP
port: 80
targetPort: 3000
type: LoadBalancer
---
# ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: microservices-ingress
namespace: microservices
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /
spec:
rules:
- host: api.yourdomain.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-gateway
port:
number: 80
---
# horizontal-pod-autoscaler.yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: users-service-hpa
namespace: microservices
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: users-service
minReplicas: 3
maxReplicas: 10
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80
Test your understanding of this topic:
Continue your learning journey and master the next set of concepts.
Continue to Module 9