Back to blog

Microservices with Node.js: Modern Architecture in 2025

Microservices are no longer buzzwords - in 2025, they're the standard architecture for scalable applications. Companies like Netflix, Uber, and Amazon run thousands of microservices in production. And Node.js? It remains the #1 choice for building these services due to its asynchronous nature and rich ecosystem.

But building microservices isn't just breaking a monolith into smaller pieces. It's about communication, resilience, observability, and coordinated deployment. It's about distributed architecture that scales without becoming chaos.

Let's explore how to build modern microservices with Node.js, from basics to advanced patterns used in production.

Anatomy of a Node.js Microservice

A well-designed microservice is small, focused, and independent:

// user-service/src/server.js
import express from 'express';
import { createClient } from 'redis';
import postgres from 'postgres';
import { Registry, collectDefaultMetrics } from 'prom-client';

const app = express();
const PORT = process.env.PORT || 3001;

// Metrics for Prometheus
const register = new Registry();
collectDefaultMetrics({ register });

// Database connection
const sql = postgres({
  host: process.env.DB_HOST,
  database: process.env.DB_NAME,
  max: 10
});

// Redis for cache
const redis = createClient({ url: process.env.REDIS_URL });
await redis.connect();

app.use(express.json());

// Health checks (essential for K8s)
app.get('/health', async (req, res) => {
  try {
    await sql`SELECT 1`;
    await redis.ping();

    res.json({
      status: 'healthy',
      timestamp: new Date().toISOString(),
      service: 'user-service'
    });
  } catch (error) {
    res.status(503).json({
      status: 'unhealthy',
      error: error.message
    });
  }
});

// Readiness probe (different from liveness!)
app.get('/ready', async (req, res) => {
  const isReady = await checkDependencies();
  if (isReady) {
    res.json({ ready: true });
  } else {
    res.status(503).json({ ready: false });
  }
});

// Metrics endpoint
app.get('/metrics', async (req, res) => {
  res.set('Content-Type', register.contentType);
  res.end(await register.metrics());
});

// API endpoints
app.get('/users/:id', async (req, res) => {
  const { id } = req.params;

  try {
    // Try cache first
    const cached = await redis.get(`user:${id}`);
    if (cached) {
      return res.json(JSON.parse(cached));
    }

    // Fetch from database
    const [user] = await sql`
      SELECT id, name, email, created_at
      FROM users WHERE id = ${id}
    `;

    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }

    // Cache for 5 minutes
    await redis.setEx(`user:${id}`, 300, JSON.stringify(user));
    res.json(user);

  } catch (error) {
    console.error('Error:', error);
    res.status(500).json({ error: 'Internal server error' });
  }
});

// Graceful shutdown
process.on('SIGTERM', async () => {
  console.log('Shutting down gracefully...');

  server.close(async () => {
    await redis.quit();
    await sql.end();
    process.exit(0);
  });

  setTimeout(() => process.exit(1), 30000);
});

const server = app.listen(PORT, () => {
  console.log(`User service on port ${PORT}`);
});

async function checkDependencies() {
  try {
    await Promise.all([sql`SELECT 1`, redis.ping()]);
    return true;
  } catch {
    return false;
  }
}

Communication Between Microservices

Two main patterns: synchronous (HTTP/gRPC) and asynchronous (messaging).

HTTP Communication with Circuit Breaker

// shared/http-client.js
import axios from 'axios';
import CircuitBreaker from 'opossum';

export class ServiceClient {
  constructor(baseURL, options = {}) {
    this.baseURL = baseURL;

    this.client = axios.create({
      baseURL,
      timeout: options.timeout || 5000
    });

    // Circuit breaker for resilience
    this.breaker = new CircuitBreaker(
      async (config) => this.client.request(config),
      {
        timeout: 5000,
        errorThresholdPercentage: 50,
        resetTimeout: 30000
      }
    );

    this.breaker.on('open', () => {
      console.warn(`Circuit OPEN for ${baseURL}`);
    });
  }

  async get(path, config = {}) {
    return this.breaker.fire({
      method: 'GET',
      url: path,
      ...config
    });
  }

  async post(path, data, config = {}) {
    return this.breaker.fire({
      method: 'POST',
      url: path,
      data,
      ...config
    });
  }
}

// Usage
const userService = new ServiceClient('http://user-service:3001');

app.get('/user-orders/:userId', async (req, res) => {
  try {
    const [userRes, ordersRes] = await Promise.all([
      userService.get(`/users/${req.params.userId}`),
      orderService.get(`/orders?userId=${req.params.userId}`)
    ]);

    res.json({
      user: userRes.data,
      orders: ordersRes.data
    });

  } catch (error) {
    if (error.message.includes('breaker is open')) {
      return res.status(503).json({ error: 'Service unavailable' });
    }
    res.status(500).json({ error: 'Internal error' });
  }
});

Microservices communication

Asynchronous Communication with RabbitMQ

// shared/message-broker.js
import amqp from 'amqplib';

export class MessageBroker {
  constructor(url) {
    this.url = url;
  }

  async connect() {
    this.connection = await amqp.connect(this.url);
    this.channel = await this.connection.createChannel();
    console.log('Connected to broker');
  }

  async publish(exchange, routingKey, message) {
    await this.channel.assertExchange(exchange, 'topic', { durable: true });

    this.channel.publish(
      exchange,
      routingKey,
      Buffer.from(JSON.stringify(message)),
      { persistent: true }
    );

    console.log(`Published: ${routingKey}`);
  }

  async subscribe(exchange, queue, routingKey, handler) {
    await this.channel.assertExchange(exchange, 'topic', { durable: true });
    await this.channel.assertQueue(queue, { durable: true });
    await this.channel.bindQueue(queue, exchange, routingKey);

    this.channel.consume(queue, async (msg) => {
      if (!msg) return;

      try {
        const content = JSON.parse(msg.content.toString());
        await handler(content);
        this.channel.ack(msg);
      } catch (error) {
        console.error('Error processing:', error);
        this.channel.nack(msg, false, false);
      }
    });

    console.log(`Subscribed: ${routingKey}`);
  }
}

// Publish event
const broker = new MessageBroker(process.env.RABBITMQ_URL);
await broker.connect();

app.post('/orders', async (req, res) => {
  const order = await createOrder(req.body);

  await broker.publish('orders', 'order.created', {
    orderId: order.id,
    userId: order.userId
  });

  res.status(201).json(order);
});

// Subscribe to events
await broker.subscribe(
  'orders',
  'notification-service',
  'order.*',
  async (event) => {
    await sendNotification(event);
  }
);

API Gateway with Express

// gateway/server.js
import express from 'express';
import httpProxy from 'http-proxy';
import jwt from 'jsonwebtoken';

const app = express();
const proxy = httpProxy.createProxyServer();

const services = {
  users: 'http://user-service:3001',
  orders: 'http://order-service:3002',
  products: 'http://product-service:3003'
};

// Authentication
const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) {
    return res.status(401).json({ error: 'No token' });
  }

  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch {
    return res.status(401).json({ error: 'Invalid token' });
  }
};

// Dynamic routing
app.use('/:service/*', authenticate, (req, res) => {
  const target = services[req.params.service];
  if (!target) {
    return res.status(404).json({ error: 'Service not found' });
  }

  req.headers['x-user-id'] = req.user.id;
  proxy.web(req, res, { target, changeOrigin: true });
});

app.listen(3000);

Deployment with Docker and Kubernetes

# Optimized Dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/node_modules ./node_modules
COPY --from=builder /app/dist ./dist

RUN addgroup -g 1001 -S nodejs && adduser -S nodejs -u 1001
USER nodejs

EXPOSE 3000
CMD ["node", "dist/server.js"]
# kubernetes/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: user-service
spec:
  replicas: 3
  selector:
    matchLabels:
      app: user-service
  template:
    metadata:
      labels:
        app: user-service
    spec:
      containers:
      - name: user-service
        image: user-service:latest
        ports:
        - containerPort: 3001
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 3001
          initialDelaySeconds: 30
        readinessProbe:
          httpGet:
            path: /ready
            port: 3001
---
apiVersion: v1
kind: Service
metadata:
  name: user-service
spec:
  selector:
    app: user-service
  ports:
  - port: 80
    targetPort: 3001

The Future of Microservices in 2025

Most exciting trends:

  • Service Mesh: Istio and Linkerd for advanced observability
  • Event-driven architectures: Kafka and event sourcing
  • Serverless microservices: AWS Lambda and Cloudflare Workers
  • Edge microservices: Globally distributed compute
  • AI-assisted orchestration: Kubernetes with AI for intelligent auto-scaling

Microservices in 2025 are about building resilient, observable, and maintainable distributed systems. Node.js continues to be the ideal choice for its performance, ecosystem, and event-driven nature.

If you want to explore more about modern architectures, I recommend reading my article about Serverless in 2025: Why Node.js Dominates (And How to Use It) where I discuss complementary architectures.

Let's go! 🦅

💻 Master JavaScript for Real

The knowledge you gained is just the beginning. There are techniques, patterns, and practices that transform beginner developers into sought-after professionals.

Payment options:

  • $4.90 (single payment)

📖 View Complete Content

Comments (0)

This article has no comments yet 😢. Be the first! 🚀🦅

Add comments