Module 10: Microservices & Architecture

Learn microservices architecture, service communication, API gateway patterns, and container orchestration for scalable Node.js applications.

Back to Course|6 hours|Advanced

Microservices & Architecture

Learn microservices architecture, service communication, API gateway patterns, and container orchestration for scalable Node.js applications.

Progress: 0/4 topics completed0%

Select Topics Overview

Microservices Architecture

Master microservices architecture principles, design patterns, and implementation strategies for building scalable Node.js applications.

Content by: Praveen Kumar

MERN Stack Developer

Connect

Microservices Fundamentals

Microservices architecture is an approach to building applications as a collection of loosely coupled, independently deployable services that communicate over well-defined APIs.

Microservices vs Monolith

Code Example
// Monolithic architecture example
const express = require('express');
const app = express();

// All services in one application
app.use('/users', userRoutes);
app.use('/orders', orderRoutes);
app.use('/payments', paymentRoutes);
app.use('/inventory', inventoryRoutes);

app.listen(3000, () => {
    console.log('Monolithic app running on port 3000');
});

// Microservices architecture
// user-service/index.js
const express = require('express');
const app = express();
app.use('/users', userRoutes);
app.listen(3001, () => {
    console.log('User service running on port 3001');
});

// order-service/index.js
const express = require('express');
const app = express();
app.use('/orders', orderRoutes);
app.listen(3002, () => {
    console.log('Order service running on port 3002');
});
Swipe to see more code

Service Design Principles

  • Single Responsibility: Each service should have one business capability
  • Autonomous: Services should be independently deployable
  • Decentralized: No single point of failure
  • Fault Tolerant: Services should handle failures gracefully
  • Observable: Services should be monitorable and debuggable
  • API-First: Design APIs before implementation

Domain-Driven Design

Code Example
// Domain-driven design for microservices
// user-service/domain/User.js
class User {
    constructor(id, email, name) {
        this.id = id;
        this.email = email;
        this.name = name;
        this.createdAt = new Date();
    }
    
    validate() {
        if (!this.email || !this.email.includes('@')) {
            throw new Error('Invalid email');
        }
        if (!this.name || this.name.length < 2) {
            throw new Error('Invalid name');
        }
    }
    
    toJSON() {
        return {
            id: this.id,
            email: this.email,
            name: this.name,
            createdAt: this.createdAt
        };
    }
}

// user-service/repository/UserRepository.js
class UserRepository {
    constructor(database) {
        this.db = database;
    }
    
    async findById(id) {
        const user = await this.db.users.findById(id);
        return user ? new User(user.id, user.email, user.name) : null;
    }
    
    async save(user) {
        user.validate();
        return await this.db.users.create(user.toJSON());
    }
}
Swipe to see more code

Service Communication Patterns

Code Example
// Synchronous communication with HTTP
const axios = require('axios');

class UserService {
    constructor() {
        this.baseURL = process.env.USER_SERVICE_URL || 'http://user-service:3001';
    }
    
    async getUser(id) {
        try {
            const response = await axios.get(`${this.baseURL}/users/${id}`);
            return response.data;
        } catch (error) {
            if (error.response?.status === 404) {
                return null;
            }
            throw new Error('Failed to fetch user');
        }
    }
}

// Asynchronous communication with message queues
const amqp = require('amqplib');

class OrderService {
    constructor() {
        this.connection = null;
        this.channel = null;
    }
    
    async connect() {
        this.connection = await amqp.connect(process.env.RABBITMQ_URL);
        this.channel = await this.connection.createChannel();
        
        // Declare queues
        await this.channel.assertQueue('order.created', { durable: true });
        await this.channel.assertQueue('order.updated', { durable: true });
    }
    
    async publishOrderCreated(order) {
        await this.channel.sendToQueue(
            'order.created',
            Buffer.from(JSON.stringify(order)),
            { persistent: true }
        );
    }
}
Swipe to see more code

Mini-Project: E-commerce Microservices

Code Example
// Complete microservices implementation
// api-gateway/index.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
const rateLimit = require('express-rate-limit');

const app = express();

// Rate limiting
const limiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100 // limit each IP to 100 requests per windowMs
});

app.use(limiter);
app.use(express.json());

// Service discovery and routing
const services = {
    user: process.env.USER_SERVICE_URL || 'http://user-service:3001',
    product: process.env.PRODUCT_SERVICE_URL || 'http://product-service:3002',
    order: process.env.ORDER_SERVICE_URL || 'http://order-service:3003',
    payment: process.env.PAYMENT_SERVICE_URL || 'http://payment-service:3004'
};

// Proxy middleware for each service
Object.entries(services).forEach(([service, target]) => {
    app.use(`/${service}`, createProxyMiddleware({
        target,
        changeOrigin: true,
        pathRewrite: {
            [`^/${service}`]: ''
        },
        onError: (err, req, res) => {
            res.status(503).json({
                error: 'Service unavailable',
                service,
                message: err.message
            });
        }
    }));
});

// Health check endpoint
app.get('/health', (req, res) => {
    res.json({
        status: 'healthy',
        timestamp: new Date().toISOString(),
        services: Object.keys(services)
    });
});

app.listen(3000, () => {
    console.log('API Gateway running on port 3000');
});

// user-service/index.js
const express = require('express');
const mongoose = require('mongoose');
const User = require('./models/User');

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

// Database connection
mongoose.connect(process.env.MONGODB_URL || 'mongodb://localhost:27017/users');

// Routes
app.get('/users/:id', async (req, res) => {
    try {
        const user = await User.findById(req.params.id);
        if (!user) {
            return res.status(404).json({ error: 'User not found' });
        }
        res.json(user);
    } catch (error) {
        res.status(500).json({ error: 'Internal server error' });
    }
});

app.post('/users', async (req, res) => {
    try {
        const user = new User(req.body);
        await user.save();
        res.status(201).json(user);
    } catch (error) {
        res.status(400).json({ error: error.message });
    }
});

app.listen(3001, () => {
    console.log('User service running on port 3001');
});
Swipe to see more code

🎯 Practice Exercise

Test your understanding of this topic:

Additional Resources

📚 Recommended Reading

  • Microservices Architecture Guide
  • API Gateway Patterns
  • Service Communication Patterns
  • Container Orchestration Best Practices

🌐 Online Resources

  • Microservices Tutorial
  • Docker Compose Guide
  • Kubernetes Documentation
  • Service Mesh Patterns

Ready for the Next Module?

Continue your learning journey and master the next set of concepts.

Continue to Module 11