Microservices in Node.js

What are Microservices?

Microservices is an architectural style where an application is composed of small, independent services that communicate over well-defined APIs.

Basic Microservice

// user-service.js
const express = require('express');
const app = express();

app.use(express.json());

app.get('/users/:id', async (req, res) => {
  const user = await User.findById(req.params.id);
  res.json(user);
});

app.post('/users', async (req, res) => {
  const user = await User.create(req.body);
  res.status(201).json(user);
});

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

Service Communication

HTTP/REST

// order-service.js
const axios = require('axios');

app.post('/orders', async (req, res) => {
  try {
    // Call user service
    const user = await axios.get(`http://user-service:3001/users/${req.body.userId}`);
    
    // Call product service
    const product = await axios.get(`http://product-service:3002/products/${req.body.productId}`);
    
    // Create order
    const order = await Order.create({
      userId: user.data.id,
      productId: product.data.id,
      total: product.data.price
    });
    
    res.status(201).json(order);
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

Message Queue (RabbitMQ)

const amqp = require('amqplib');

// Publisher
async function publishMessage(queue, message) {
  const connection = await amqp.connect('amqp://localhost');
  const channel = await connection.createChannel();
  
  await channel.assertQueue(queue);
  channel.sendToQueue(queue, Buffer.from(JSON.stringify(message)));
  
  await channel.close();
  await connection.close();
}

// Consumer
async function consumeMessages(queue, callback) {
  const connection = await amqp.connect('amqp://localhost');
  const channel = await connection.createChannel();
  
  await channel.assertQueue(queue);
  channel.consume(queue, (msg) => {
    const data = JSON.parse(msg.content.toString());
    callback(data);
    channel.ack(msg);
  });
}

// Usage
await publishMessage('orders', { orderId: 123, userId: 456 });

await consumeMessages('orders', (order) => {
  console.log('Processing order:', order);
});

API Gateway

const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');

const app = express();

// Route to user service
app.use('/api/users', createProxyMiddleware({
  target: 'http://user-service:3001',
  changeOrigin: true
}));

// Route to product service
app.use('/api/products', createProxyMiddleware({
  target: 'http://product-service:3002',
  changeOrigin: true
}));

// Route to order service
app.use('/api/orders', createProxyMiddleware({
  target: 'http://order-service:3003',
  changeOrigin: true
}));

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

Service Discovery

const consul = require('consul')();

// Register service
async function registerService() {
  await consul.agent.service.register({
    name: 'user-service',
    address: 'localhost',
    port: 3001,
    check: {
      http: 'http://localhost:3001/health',
      interval: '10s'
    }
  });
}

// Discover service
async function discoverService(serviceName) {
  const result = await consul.health.service(serviceName);
  return result[0].Service;
}

Circuit Breaker

const CircuitBreaker = require('opossum');

const options = {
  timeout: 3000,
  errorThresholdPercentage: 50,
  resetTimeout: 30000
};

async function callUserService(userId) {
  const response = await axios.get(`http://user-service:3001/users/${userId}`);
  return response.data;
}

const breaker = new CircuitBreaker(callUserService, options);

breaker.fallback(() => ({ id: null, name: 'Unknown' }));

breaker.on('open', () => console.log('Circuit opened'));
breaker.on('halfOpen', () => console.log('Circuit half-open'));
breaker.on('close', () => console.log('Circuit closed'));

// Usage
const user = await breaker.fire(userId);

Docker Compose

version: '3.8'

services:
  user-service:
    build: ./user-service
    ports:
      - "3001:3001"
    environment:
      - DATABASE_URL=mongodb://mongo:27017/users
    depends_on:
      - mongo
  
  product-service:
    build: ./product-service
    ports:
      - "3002:3002"
    environment:
      - DATABASE_URL=mongodb://mongo:27017/products
    depends_on:
      - mongo
  
  order-service:
    build: ./order-service
    ports:
      - "3003:3003"
    environment:
      - DATABASE_URL=mongodb://mongo:27017/orders
    depends_on:
      - mongo
  
  api-gateway:
    build: ./api-gateway
    ports:
      - "3000:3000"
    depends_on:
      - user-service
      - product-service
      - order-service
  
  mongo:
    image: mongo:latest
    ports:
      - "27017:27017"

Best Practices

  1. Single responsibility per service
  2. Independent deployment
  3. Decentralized data management
  4. API gateway for routing
  5. Service discovery
  6. Circuit breakers for resilience
  7. Monitoring and logging

Interview Tips

  • Explain microservices: Independent, loosely coupled services
  • Show communication: HTTP, message queues
  • Demonstrate API gateway: Centralized routing
  • Discuss patterns: Circuit breaker, service discovery
  • Mention challenges: Distributed transactions, debugging

Summary

Microservices architecture splits applications into small, independent services. Communicate via HTTP/REST or message queues. Use API gateway for routing, service discovery for location, circuit breakers for resilience. Deploy with Docker/Kubernetes.

Test Your Knowledge

Take a quick quiz to test your understanding of this topic.

Test Your Node.js Knowledge

Ready to put your skills to the test? Take our interactive Node.js quiz and get instant feedback on your answers.