Skip to main content

Chapter 8: Microservices

Microservices architecture enables you to build scalable, distributed systems. NestJS provides first-class support for microservices, message brokers, and event-driven design. This chapter covers TCP, Redis, RabbitMQ, message patterns, distributed tracing, and real-world deployment tips. We’ll walk through practical examples and explain how to design robust microservices.

8.1 What is a Microservice?

A microservice is a small, independent process that communicates with others over a network. Each service is responsible for a specific business capability and can be deployed, scaled, and updated independently.

Microservices vs Monolith

Monolithic Architecture:
  • Single codebase
  • Deploy as one unit
  • Scale entire application
  • Shared database
Microservices Architecture:
  • Multiple independent services
  • Deploy services independently
  • Scale individual services
  • Each service has its own database
Analogy:
Imagine a city’s postal system. Each microservice is a different office (tax office, passport office, vehicle registration). Each office has its own building, its own staff, and its own filing system (database). They communicate by sending letters (messages) through the postal service (message broker). If the tax office burns down, the passport office keeps working. You can hire more staff at the tax office during tax season without expanding the passport office. But managing a city full of independent offices is inherently more complex than managing one big building — you need reliable mail delivery, consistent address formats, and a way to track letters across offices. That is the fundamental trade-off of microservices: operational complexity in exchange for independent scalability and resilience.

Benefits

Scalability:
  • Scale only what you need
  • Independent scaling per service
  • Optimize resources
Fault Isolation:
  • One service can fail without crashing the whole system
  • Isolated failures
  • Better resilience
Technology Diversity:
  • Use the best tool for each job
  • Different languages/frameworks per service
  • Technology flexibility
Independent Deployment:
  • Deploy and update services separately
  • Faster release cycles
  • Reduced risk
Team Autonomy:
  • Teams can work independently
  • Clear ownership
  • Faster development

Challenges

These are not hypothetical downsides — they are the daily reality of running microservices in production. Do not adopt microservices unless these challenges are worth the benefits for your specific situation.
  • Complexity: More moving parts means more things to deploy, monitor, version, and debug. A monolith with 10 modules becomes 10 services with 10 deployment pipelines, 10 health checks, and 10 log streams.
  • Network Latency: Every service-to-service call crosses the network. What was a 1ms in-process function call becomes a 5-50ms network round trip. Chained calls compound this — Service A calls B calls C means 3x latency.
  • Data Consistency: Without distributed transactions (which are hard), you rely on eventual consistency and saga patterns. This means your system may temporarily show stale or inconsistent data.
  • Testing: You cannot just run npm test — you need the entire ecosystem running (or good contract tests). E2E testing across services requires orchestration tools like Docker Compose.
  • Monitoring: A single request may touch 5 services. Without distributed tracing (OpenTelemetry, Jaeger), debugging production issues is like finding a needle in a haystack.
Honest Advice: Start with a well-structured monolith. Extract services only when you have a concrete scaling or team-autonomy reason. Most NestJS applications never need microservices — a modular monolith with clear module boundaries gives you 80% of the benefits with 20% of the complexity.

8.2 Microservices in NestJS

NestJS supports multiple transport layers for microservices, each with different characteristics.

Supported Transports

TCP:
  • Default, simple and fast
  • Direct service-to-service communication
  • Low latency
  • Good for internal services
Redis:
  • Pub/sub messaging
  • Caching support
  • Simple setup
  • Good for event-driven systems
NATS:
  • Lightweight messaging
  • High performance
  • Simple protocol
  • Good for cloud-native apps
RabbitMQ:
  • Robust message broker
  • Advanced routing
  • Reliable delivery
  • Good for complex workflows
MQTT:
  • IoT messaging
  • Lightweight protocol
  • Good for IoT applications
gRPC:
  • High-performance RPC
  • Type-safe
  • HTTP/2 based
  • Good for inter-service communication
Kafka:
  • Distributed streaming
  • High throughput
  • Event sourcing support
  • Good for big data

Transport Layer Comparison

Decision Framework — Which Transport?
Monolith vs Microservices Decision Framework:

8.3 Creating a TCP Microservice

TCP is the simplest transport for microservices. Let’s create a basic TCP microservice.

Installation

Microservice Bootstrap

Message Pattern Handler

Message patterns are the microservice equivalent of HTTP routes. Instead of @Get('/users/:id'), you use @MessagePattern({ cmd: 'get_user' }). The pattern object is matched exactly — think of it as a topic or address for the message.
Key Distinction: @MessagePattern is for request-response (caller waits for a reply). @EventPattern is for fire-and-forget (caller does not wait). Choosing the wrong one is a common mistake — if you use @MessagePattern but the caller uses .emit(), the handler will never fire.

Client Service

Hybrid Application

Run both HTTP and microservice in the same app:

8.4 Message Patterns

Message patterns define how microservices communicate. Use consistent patterns for maintainability.

Request-Response Pattern

Event Pattern (Fire and Forget)

Multiple Patterns

Tip: Use clear, consistent message patterns for maintainability. Document your message contracts.

8.5 Using RabbitMQ

RabbitMQ is a popular message broker for event-driven systems. It enables publish/subscribe and queue-based communication.

Installation

RabbitMQ Microservice

RabbitMQ Client

Diagram: Event-Driven Flow

8.6 Using Redis

Redis provides pub/sub messaging and can be used as a transport layer.

Redis Microservice

Redis Client


8.7 Event-Driven Design

Event-driven architecture decouples services and enables scalability.

Principles

Decoupling:
  • Services don’t need to know about each other
  • Communicate via events
  • Loose coupling
Scalability:
  • Publish/subscribe patterns
  • Multiple consumers
  • Horizontal scaling
Reliability:
  • Message persistence
  • Retries and dead-letter queues
  • Event replay

Event Sourcing Integration

Correlation IDs

Track requests across services:

Saga Pattern for Distributed Transactions

When an operation spans multiple services (e.g., “create order, charge payment, reserve inventory”), you cannot use a database transaction because each service has its own database. The Saga pattern coordinates these steps with compensation logic for failures.

8.8 Distributed Tracing & Monitoring

Monitor and trace requests across microservices.

OpenTelemetry Integration

Logging with Correlation IDs


8.9 Error Handling

Handle errors gracefully in microservices.

Error Handling Pattern

Retry Logic

In distributed systems, transient failures (network blips, temporary service unavailability) are normal, not exceptional. Retrying with backoff is the standard response — but be careful about retrying non-idempotent operations. Retrying a “charge credit card” command without idempotency keys will double-charge your customer.
Production Pattern: Combine retries with circuit breakers. After N consecutive failures, the circuit breaker “opens” and stops making requests for a cooldown period, preventing cascade failures across services. Libraries like opossum integrate well with NestJS.

Error Handling Decision Framework for Microservices


8.10 Best Practices

Following best practices ensures your microservices are robust and maintainable.

Service Design

  • Keep services small and focused (single responsibility)
  • Define clear service boundaries
  • Use domain-driven design
  • Document service contracts

Communication

  • Use message brokers for async communication
  • Use request-response for synchronous needs
  • Implement circuit breakers
  • Handle timeouts gracefully

Data Management

  • Each service owns its data
  • Avoid shared databases
  • Use event sourcing for consistency
  • Implement saga pattern for distributed transactions

Security

  • Secure communication between services (TLS)
  • Authenticate service-to-service calls
  • Use API keys or mTLS
  • Validate all inputs

Deployment

  • Containerize services (Docker)
  • Use orchestration (Kubernetes)
  • Implement health checks
  • Enable auto-scaling

Monitoring

  • Log all important events
  • Use distributed tracing
  • Monitor latency and errors
  • Set up alerts

Documentation

  • Document message patterns
  • Document service APIs
  • Maintain service registry
  • Keep architecture diagrams updated

8.11 Summary

You’ve learned how to build and scale microservices with NestJS: Key Concepts:
  • Microservices: Independent, scalable services
  • Transport Layers: TCP, Redis, RabbitMQ, NATS, gRPC
  • Message Patterns: Request-response and event patterns
  • Event-Driven: Decoupled, scalable architecture
  • Distributed Tracing: Monitor requests across services
Best Practices:
  • Keep services small and focused
  • Use message brokers for communication
  • Handle errors gracefully
  • Secure service communication
  • Monitor and trace requests
  • Document service contracts
Next Chapter: Learn about deployment, production optimization, Docker, Kubernetes, and monitoring strategies.