NestJS — Enterprise Node.js

Microservices Architecture & Transporters

16 min Lesson 36 of 48

Microservices Architecture & Transporters

Microservices decompose a monolithic application into small, independently deployable services that communicate over a network. NestJS has first-class support for building microservices through @nestjs/microservices, which provides a uniform API over several underlying transporters — the communication channels between services.

Why Microservices?

  • Independent scaling — scale only the services under load, not the whole app.
  • Fault isolation — a crash in one service does not bring down the entire system.
  • Technology freedom — each service can use a different language or database.
  • Team autonomy — separate teams can own and deploy individual services.
Microservices add operational complexity. Service discovery, network latency, distributed tracing, and eventual consistency are real costs. Only reach for this pattern when a monolith's pain (deployment coupling, scaling bottlenecks) clearly outweighs the overhead.

Creating a Microservice with NestFactory.createMicroservice

Instead of NestFactory.create(), you call NestFactory.createMicroservice(), passing your root module and a transport options object. The microservice listens for messages rather than HTTP requests:

import { NestFactory } from '@nestjs/core'; import { Transport, MicroserviceOptions } from '@nestjs/microservices'; import { AppModule } from './app.module'; async function bootstrap() { const app = await NestFactory.createMicroservice<MicroserviceOptions>( AppModule, { transport: Transport.TCP, options: { host: '127.0.0.1', port: 3001 }, }, ); await app.listen(); console.log('Microservice is listening on port 3001'); } bootstrap();

Message Patterns & Handlers

Controllers in a microservice use @MessagePattern() to register handlers for incoming messages. The pattern is a contract between the caller and the service — a string, number, or object:

import { Controller } from '@nestjs/common'; import { MessagePattern, Payload } from '@nestjs/microservices'; @Controller() export class MathController { @MessagePattern({ cmd: 'sum' }) sum(@Payload() data: number[]): number { return data.reduce((a, b) => a + b, 0); } }

For fire-and-forget notifications (no response expected), use @EventPattern() instead of @MessagePattern().

TCP Transporter

TCP is the simplest transporter — direct socket communication between services on the same network. It is reliable and low-latency but requires both services to be reachable by hostname/IP. Use it for internal, co-located services.

TCP is great for development and simple internal networks. For production systems requiring message persistence, fan-out, or replay, prefer a broker-based transporter like Redis, RabbitMQ, or Kafka.

Redis Transporter

The Redis transporter uses Redis pub/sub to relay messages. The caller and the handler do not need direct network access to each other — they both connect to the Redis server. This decouples services and enables fan-out (multiple subscribers to one event):

// microservice bootstrap with Redis const app = await NestFactory.createMicroservice<MicroserviceOptions>( AppModule, { transport: Transport.REDIS, options: { host: 'localhost', port: 6379 }, }, ); // client in another service import { ClientProxy, ClientProxyFactory, Transport } from '@nestjs/microservices'; const client: ClientProxy = ClientProxyFactory.create({ transport: Transport.REDIS, options: { host: 'localhost', port: 6379 }, }); // send a message and await the response (Observable) client.send({ cmd: 'sum' }, [1, 2, 3]).subscribe(result => { console.log('Result:', result); // 6 });

Hybrid Applications (HTTP + Microservice)

A single NestJS process can act as both an HTTP server and a microservice listener. Call app.connectMicroservice() before app.startAllMicroservices() and app.listen():

const app = await NestFactory.create(AppModule); app.connectMicroservice<MicroserviceOptions>({ transport: Transport.TCP, options: { port: 3001 }, }); await app.startAllMicroservices(); await app.listen(3000); // HTTP on :3000, TCP microservice on :3001
Do not confuse hybrid apps with a true microservice architecture. Combining HTTP and a microservice listener in one process is useful for a migration path or for a service that must expose both interfaces. In a greenfield microservices system, keep each service focused on one responsibility.

Summary

NestJS microservices are bootstrapped with NestFactory.createMicroservice() and a transporter configuration. The TCP transporter provides direct socket communication; the Redis transporter decouples services through a broker. Handlers are decorated with @MessagePattern() (request-response) or @EventPattern() (fire-and-forget). Hybrid applications run HTTP and a microservice listener in the same process, enabling gradual decomposition of a monolith.