Suggest an editImprove this articleRefine the answer for “How do Microservices work in NestJS?”. Your changes go to moderation before they’re published.Approval requiredContentWhat you’re changing🇺🇸EN🇺🇦UAPreviewTitle (EN)Short answer (EN)**NestJS microservices** are services that communicate through transports like TCP, Redis, or Kafka using `@MessagePattern` instead of HTTP routes. ```typescript const app = await NestFactory.createMicroservice<MicroserviceOptions>(AppModule, { transport: Transport.TCP, options: { host: 'localhost', port: 3001 }, }); await app.listen(); ``` **Key:** the same NestJS DI and module system, but message patterns replace URL routes and transport replaces HTTP.Shown above the full answer for quick recall.Answer (EN)Image**NestJS microservices** are independent services that communicate through message transports like TCP, Redis, or gRPC, using the same decorators and modules as regular HTTP apps but without Express or Koa underneath. ## Theory ### TL;DR - Think of it like a kitchen brigade: the API gateway (head waiter) takes HTTP requests and dispatches them as messages to specialized stations (math-service, user-service), each listening on its own transport. - Main difference: HTTP controllers use `@Get`/`@Post`; microservice controllers use `@MessagePattern` and listen on TCP, Redis, or Kafka instead of HTTP. - Two communication styles: request-response (`@MessagePattern` + `client.send()`) and fire-and-forget (`@EventPattern` + `client.emit()`). - Decision rule: two or more services need to scale independently or talk without HTTP? Use microservices. Single team, single deploy? Stay with HTTP controllers. - `@nestjs/microservices` ships seven built-in transports: TCP, Redis, NATS, RabbitMQ, Kafka, gRPC, and MQTT. ### Quick example A TCP math service: server listens on port 3001, client sends two numbers and gets the sum back. ```typescript // math-service/main.ts import { NestFactory } from '@nestjs/core'; import { MicroserviceOptions, Transport } from '@nestjs/microservices'; import { MathModule } from './math.module'; async function bootstrap() { const app = await NestFactory.createMicroservice<MicroserviceOptions>(MathModule, { transport: Transport.TCP, options: { host: 'localhost', port: 3001 }, }); await app.listen(); // no HTTP port, just TCP } bootstrap(); ``` ```typescript // math.controller.ts import { Controller } from '@nestjs/common'; import { MessagePattern, Payload } from '@nestjs/microservices'; @Controller() export class MathController { @MessagePattern({ cmd: 'add' }) // matches client.send({ cmd: 'add' }, ...) add(@Payload() data: number[]): number { return data.reduce((a, b) => a + b, 0); // returns 5 for [2, 3] } } ``` No `@Get`, no HTTP server, no Express. Just a pattern that maps to a handler. ### Key difference from HTTP controllers HTTP controllers bind to URL paths and HTTP verbs. Microservice controllers bind to message patterns and transports. When a client calls `client.send({ cmd: 'add' }, [2, 3])`, NestJS serializes the payload to JSON (or Protobuf in gRPC), sends it over the wire, and the matching `@MessagePattern` handler deserializes and processes it. The response travels back the same way. This separation means you can swap the transport without touching your business logic. A handler working over TCP today can run over RabbitMQ tomorrow with a config change in `main.ts`. ### @MessagePattern vs @EventPattern These two decorators cover almost every communication need: | Decorator | Client method | Gets a reply? | Typical use | |---|---|---|---| | `@MessagePattern` | `client.send()` | Yes (Observable) | Fetch user data, calculate sum | | `@EventPattern` | `client.emit()` | No | Log order created, notify warehouse | `client.send()` returns an RxJS Observable. You await it with `.toPromise()` or `firstValueFrom()`. `client.emit()` returns `void`, it fires and does not wait. ```typescript // API gateway using both patterns @Get('sum') async sum() { return this.mathClient .send({ cmd: 'add' }, [1, 2, 3]) // waits for reply .toPromise(); } @Post('order') async order(@Body() body: CreateOrderDto) { this.orderClient.emit('order_created', body); // fire and forget return { status: 'queued' }; } ``` ### Transport options Choosing a transport means choosing a tradeoff between latency, throughput, and delivery guarantees. | Transport | Latency | Throughput | Delivery | Best for | |---|---|---|---|---| | **TCP** | Low | Medium | At-most-once | Internal RPC, simple setups | | **Redis** | Low | High | At-most-once | Pub/sub, caching-adjacent events | | **NATS** | Very low | Very high | At-least-once | Real-time fan-out, high volume | | **RabbitMQ** | Medium | High | Exactly-once (with ACKs) | Order queues, anything needing ACK | | **Kafka** | Medium | Extreme | At-least-once | Event logs, replayable streams, >1M msg/sec | | **gRPC** | Very low | High | At-most-once | Type-safe APIs, Protobuf, polyglot systems | Start with TCP when splitting a monolith and keeping everything inside one network. Move to Kafka or RabbitMQ when you need durability and replay. ### How it works internally NestJS creates a `Server` instance per transport. For TCP that is a Node.js `net.Server` bound to the configured port. When a message arrives, NestJS deserializes the buffer (JSON by default), matches the pattern against registered handlers via its internal `MessageBroker`, and calls the handler. The response serializes back through the same connection. Node's event loop handles everything async, no threads involved. For streaming transports like Kafka and NATS, handlers can return RxJS Observables, which NestJS pipes back to the client as a stream. gRPC is a separate case: it requires `.proto` files for code generation. Pattern registration and handler lookup still work the same way, but payloads go through Protobuf encoding instead of JSON. Benchmarks from NestJS performance tests show roughly 7x throughput improvement over JSON/TCP for binary-heavy payloads. ### Hybrid application Sometimes you need HTTP and a message transport in the same process. For example, an API gateway that exposes REST to the outside world but also listens on Redis for internal events. ```typescript // main.ts async function bootstrap() { const app = await NestFactory.create(AppModule); // HTTP server app.connectMicroservice({ transport: Transport.REDIS, options: { host: 'localhost', port: 6379 }, }); await app.startAllMicroservices(); // start Redis listener await app.listen(3000); // start HTTP server } ``` Both run in the same NestJS instance. Controllers with `@Get` handle HTTP. Controllers with `@MessagePattern` handle Redis messages. One common mistake here: passing transport options directly to `NestFactory.create` instead of calling `connectMicroservice`. The TCP option gets silently ignored that way. ### Common mistakes **Using `@Get` in a microservice:** ```typescript @Get('add') // ignored in TCP mode - no Express, no route resolution add() { ... } ``` Fix: `@MessagePattern({ cmd: 'add' })`. **Forgetting to ACK in RabbitMQ:** ```typescript @MessagePattern('process') process(@Payload() data: any) { doWork(data); // no ack - message redelivers on crash, infinite loop } ``` This is the number one RabbitMQ issue in NestJS projects. Fix: inject `@Ctx() ctx: RmqContext` and call `ctx.getChannelRef().ack(ctx.getMessage())` after your work completes. Also set `noAck: false` in bootstrap options. ```typescript @MessagePattern('process') process(@Payload() data: any, @Ctx() ctx: RmqContext) { doWork(data); ctx.getChannelRef().ack(ctx.getMessage()); // RabbitMQ: message handled, do not redeliver } ``` **Hybrid app without `connectMicroservice`:** ```typescript // Wrong: creates only an HTTP server, TCP option is ignored const app = await NestFactory.create(AppModule, { transport: Transport.TCP }); ``` Use `app.connectMicroservice({...})` after `NestFactory.create`. **Sending Buffers over JSON transport:** ```typescript client.send({ cmd: 'upload' }, Buffer.from(imageData)); // corrupts data ``` `JSON.stringify` does not handle Buffers correctly. Use a custom `serializer` or switch to gRPC with Protobuf for binary payloads. **Shared state across instances:** Two instances of the same microservice with a local cache will get out of sync under load. Keep handlers stateless. Put state in Redis, a database, or a message store. ### Real-world usage - **Internal RPC**: user-service and order-service on TCP inside a Kubernetes cluster, no public exposure. - **E-commerce order flow**: order-service emits `order_created` to RabbitMQ; notification-service and inventory-service both consume it independently. - **Event log**: ride-hailing apps log every driver location update as a Kafka event. Services replay the log to rebuild state after restarts. - **gRPC gateway**: an API gateway proxies typed requests from mobile clients to internal services via Protobuf, similar to how Google and Netflix structure internal comms. - **Strangler migration**: run HTTP and TCP on the same app via `connectMicroservice`, then peel off services one by one as each proves stable in production. ### Follow-up questions **Q:** What is the difference between `@MessagePattern` and `@EventPattern`? **A:** `@MessagePattern` is request-response, the client awaits a reply. `@EventPattern` is fire-and-forget, the client emits and moves on, no reply expected. Use `@MessagePattern` for queries and commands needing confirmation; use `@EventPattern` for notifications. **Q:** How does NestJS handle transport failures? **A:** Configure `retryAttempts` and `retryDelay` in client options. Under the hood NestJS applies RxJS `retryWhen` on the Observable. For persistent delivery you still need a broker that supports ACKs, like RabbitMQ or Kafka. **Q:** How do you run HTTP and a microservice transport at the same time? **A:** Use `NestFactory.create` for HTTP, then `app.connectMicroservice({...})` for the second transport, then call `app.startAllMicroservices()` before `app.listen()`. Both run in the same process. NestJS routes HTTP to `@Get`/`@Post` handlers and messages to `@MessagePattern` handlers. **Q:** When does the TCP vs gRPC difference actually matter in production? **A:** For internal JSON payloads at moderate load, TCP is simpler and sufficient. gRPC matters when payloads are binary-heavy, when you need strict contract enforcement via `.proto` files, or when talking to services in other languages. Protobuf encoding runs about 7x faster than JSON for large binary messages. **Q:** Design a resilient order service with Kafka. How do you handle out-of-order events and ensure exactly-once processing? **A:** Partition Kafka topics by `orderId` so all events for one order go to the same partition in sequence. Use consumer group IDs to avoid duplicate processing across instances. For exactly-once semantics, enable Kafka transactions and use idempotent handlers: store a processed event UUID in Redis before acting, skip duplicates on retry. Handle out-of-order events by versioning the order state and rejecting events with an older version number than what is stored. ## Examples ### Basic TCP microservice A minimal setup: one service handles math, one gateway calls it over HTTP. ```typescript // math-service/math.controller.ts import { Controller } from '@nestjs/common'; import { MessagePattern, Payload } from '@nestjs/microservices'; @Controller() export class MathController { @MessagePattern({ cmd: 'sum' }) sum(@Payload() numbers: number[]): number { return numbers.reduce((acc, n) => acc + n, 0); } } ``` ```typescript // api-gateway/gateway.controller.ts import { Controller, Get, Inject } from '@nestjs/common'; import { ClientProxy } from '@nestjs/microservices'; import { firstValueFrom } from 'rxjs'; @Controller('math') export class GatewayController { constructor(@Inject('MATH_SERVICE') private client: ClientProxy) {} @Get('sum') async sum() { const result = await firstValueFrom( this.client.send({ cmd: 'sum' }, [1, 2, 3, 4, 5]) ); return { result }; // { result: 15 } } } ``` Register `MATH_SERVICE` in `ClientsModule.register([{ name: 'MATH_SERVICE', transport: Transport.TCP, options: { host: 'localhost', port: 3001 } }])`. The gateway stays HTTP; the math service is pure TCP. ### RabbitMQ with manual ACK (e-commerce order flow) Order-service publishes, user-service consumes and acknowledges manually to avoid infinite redelivery on crash. ```typescript // user-service/user.controller.ts import { Controller } from '@nestjs/common'; import { MessagePattern, Payload, Ctx, RmqContext } from '@nestjs/microservices'; @Controller() export class UserController { @MessagePattern('user_create') async create( @Payload() data: { email: string }, @Ctx() ctx: RmqContext, ) { const channel = ctx.getChannelRef(); const originalMsg = ctx.getMessage(); try { const user = await this.userService.create(data.email); channel.ack(originalMsg); // done, do not redeliver return { id: user.id, email: user.email }; } catch (err) { channel.nack(originalMsg, false, true); // requeue on failure throw err; } } } ``` ```typescript // user-service/main.ts bootstrap config { transport: Transport.RMQ, options: { urls: ['amqp://localhost:5672'], queue: 'user_queue', noAck: false, // required for manual ACK to work }, } ``` Without `noAck: false` and the explicit `channel.ack`, every crash redelivers the message indefinitely. ### gRPC streaming (senior-level) gRPC allows streaming responses, not just single replies. This requires a `.proto` file and the `@grpc/grpc-js` package. ```protobuf // math.proto syntax = "proto3"; package math; service MathService { rpc StreamNumbers(NumbersRequest) returns (stream NumberResponse); } message NumbersRequest { repeated int32 numbers = 1; } message NumberResponse { int32 result = 1; } ``` ```typescript // math.controller.ts import { Controller } from '@nestjs/common'; import { GrpcMethod } from '@nestjs/microservices'; import { Observable, from } from 'rxjs'; import { map } from 'rxjs/operators'; @Controller() export class MathController { @GrpcMethod('MathService', 'StreamNumbers') streamNumbers(data: { numbers: number[] }): Observable<{ result: number }> { // emits one response per number, streams back to client return from(data.numbers).pipe( map(n => ({ result: n * n })) ); } } ``` Common pitfall: forgetting to install `@grpc/grpc-js` or misconfiguring the `protoPath` gives a silent "method not found" error at runtime, not at compile time. Check the package and the `protoPath` in your `GrpcOptions` first.For the reviewerNote to the moderator (optional)Visible only to the moderator. Helps review go faster.