Skip to content
All essays
ArchitectureJanuary 22, 202515 min

Microservices Architecture with Node.js: Scalable Systems

Learn to build scalable microservices with Node.js. From service design to inter-service communication and deployment.

Ü
Ümit Uz
Mobile & Full Stack Developer

What are Microservices?

Microservices architecture breaks applications into small, independent services that communicate via APIs.

Benefits

  • Scalability: Scale individual services independently
  • Flexibility: Use different technologies per service
  • Resilience: Failure in one service doesn't crash the entire app
  • Deployability: Deploy services independently

Design Principles

Single Responsibility

Each service should have a single, well-defined purpose.

javascript
// User Service - handles user management
// Order Service - handles orders
// Payment Service - handles payments
// Notification Service - handles notifications

Decentralized Data Management

Each service owns its data.

javascript
// User Service has users table
// Order Service has orders table
// Payment Service has payments table

Service Communication

REST API

javascript
// Order Service calls User Service
const user = await axios.get(
  'http://user-service/users/123'
);

Message Queues

javascript
// RabbitMQ with amqplib
import amqp from 'amqplib';

const connection = await amqp.connect('amqp://localhost');
const channel = await connection.createChannel();

// Send message
channel.sendToQueue('orders', Buffer.from(JSON.stringify(order)));

// Consume messages
channel.consume('orders', (msg) => {
  const order = JSON.parse(msg.content.toString());
  processOrder(order);
});

gRPC

javascript
// Faster, typed communication
const client = new userServiceClient(
  'user-service:50051',
  creds.createInsecure()
);

const user = await client.getUser({ id: '123' });

API Gateway

An API gateway routes requests to appropriate services.

javascript
import express from 'express';
const gateway = express();

gateway.use('/users', proxy('http://user-service'));
gateway.use('/orders', proxy('http://order-service'));
gateway.use('/payments', proxy('http://payment-service'));

Service Discovery

Services need to find each other dynamically.

javascript
// Using Consul
import Consul from 'consul';

const consul = new Consul();

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

// Discover service
const services = await consul.agent.service.list();
const userService = services['user-service'];

Resilience Patterns

Circuit Breaker

javascript
import CircuitBreaker from 'opossum';

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

const breaker = new CircuitBreaker(callService, options);

breaker.fallback(() => ({ error: 'Service unavailable' }));

Retry

javascript
import retry from 'async-retry';

await retry(
  async () => {
    const response = await fetch(url);
    if (!response.ok) throw new Error('Failed');
    return response.json();
  },
  {
    retries: 3,
    minTimeout: 1000,
    maxTimeout: 5000
  }
);

Deployment

Docker

dockerfile
# user-service/Dockerfile
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
CMD ["node", "server.js"]

Docker Compose

yaml
version: '3.8'
services:
  user-service:
    build: ./user-service
    ports:
      - "3001:3001"
    environment:
      - DB_URL=mongodb://mongodb:27017
    depends_on:
      - mongodb

  mongodb:
    image: mongo:latest
    ports:
      - "27017:27017"

Monitoring

javascript
// Health check endpoint
app.get('/health', (req, res) => {
  res.json({
    status: 'healthy',
    uptime: process.uptime(),
    timestamp: Date.now()
  });
});

// Metrics
import promClient from 'prom-client';
const register = new promClient.Registry();
promClient.collectDefaultMetrics({ register });

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

Best Practices

  1. 1Keep services small: Focus on single responsibility
  2. 2Use async communication: Message queues for decoupling
  3. 3Implement circuit breakers: Prevent cascading failures
  4. 4Centralize logging: Aggregate logs from all services
  5. 5Use API versioning: Maintain backward compatibility

Conclusion

Microservices architecture with Node.js enables building scalable, maintainable systems. Start small and evolve as needed.

Related essays

Next essay
Redis Caching: Complete Guide to Performance Optimization