How to Implement Microservices with NestJS: A Complete Guide to TCP, Redis, and Beyond

To implement microservices with NestJS, use NestFactory.createMicroservice() to bootstrap a transport-agnostic server, decorate handlers with @MessagePattern or @EventPattern, and inject ClientProxy instances via the @Client() decorator or ClientsModule for inter-service communication.

NestJS treats microservices as first-class citizens within the nestjs/nest ecosystem. The framework's @nestjs/microservices package provides a complete toolkit for building distributed systems using various transport layers including TCP, Redis, NATS, Kafka, and gRPC. Understanding the core architecture—specifically how the ListenersController registers pattern handlers and how the IoC container manages client proxies—is essential for production-grade implementations.

Bootstrapping a Microservice Application

Instead of the standard HTTP bootstrap using NestFactory.create(), microservices require NestFactory.createMicroservice() or a hybrid approach using createNestApplication() combined with connectMicroservice().

// main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { Transport } from '@nestjs/microservices';

async function bootstrap() {
  const app = await NestFactory.createMicroservice(AppModule, {
    transport: Transport.TCP,
    options: { host: '0.0.0.0', port: 8877 },
  });
  await app.listen();
}
bootstrap();

The transport configuration determines which concrete Server implementation (such as ServerTCP, ServerRedis, or ServerNATS) the framework instantiates. These implementations adhere to the generic Server interface defined in packages/microservices/server/server.ts, ensuring consistent behavior across different messaging systems.

Alternatively, create a hybrid application that handles both HTTP and microservice traffic:

const app = await NestFactory.create(AppModule);
const microservice = app.connectMicroservice({
  transport: Transport.TCP,
  options: { port: 8877 },
});
await app.startAllMicroservices();
await app.listen(3000);

Registering Message Handlers with Decorators

Controllers in microservices use @MessagePattern() for request-response RPC flows or @EventPattern() for event-driven architectures. The ListenersController in packages/microservices/listeners-controller.ts orchestrates the registration of these handlers during application initialization.

Pattern scanning process:

  • MetadataScanner inspects controller metadata
  • ListenerMetadataExplorer identifies decorated methods
  • registerPatternHandlers() (lines 61-78) binds patterns to the underlying Server instance
// app.controller.ts
import { Controller } from '@nestjs/common';
import { MessagePattern, EventPattern } from '@nestjs/microservices';

@Controller()
export class AppController {
  @MessagePattern({ cmd: 'sum' })
  sum(data: number[]): number {
    return (data || []).reduce((a, b) => a + b);
  }

  @EventPattern('user_created')
  async handleUserCreated(data: Record<string, any>) {
    // Event-driven logic here
  }
}

Source: app.controller.ts

Injecting Client Proxies for Inter-Service Communication

Microservices communicate through ClientProxy instances, which abstract the underlying transport protocol. NestJS provides two injection mechanisms: the property decorator @Client() for simple cases and ClientsModule for complex, asynchronous configurations.

Method 1: Using the @Client() decorator

The @Client() decorator in packages/microservices/decorators/client.decorator.ts marks properties for proxy injection. During initialization, ListenersController.assignClientsToProperties() (lines 4-16) creates clients via ClientProxyFactory and stores them in the ClientsContainer.

import { Controller, Post, Body } from '@nestjs/common';
import { Client, ClientProxy, Transport } from '@nestjs/microservices';
import { first } from 'rxjs/operators';

@Controller()
export class AppController {
  @Client({ transport: Transport.TCP })
  client: ClientProxy;

  @Post('add')
  async add(@Body() numbers: number[]): Promise<number> {
    return this.client
      .send<number>({ cmd: 'sum' }, numbers)
      .pipe(first())
      .toPromise();
  }
}

Method 2: Using ClientsModule for async configuration

For dynamic configurations requiring dependency injection (such as environment-based transport settings), use ClientsModule.registerAsync():

// app.module.ts
import { Module, Injectable } from '@nestjs/common';
import { ClientsModule, Transport, ClientOptions, ClientsModuleOptionsFactory } from '@nestjs/microservices';
import { ConfigService } from './config.service';

@Injectable()
class ClientOptionService implements ClientsModuleOptionsFactory {
  constructor(private readonly config: ConfigService) {}
  createClientOptions(): ClientOptions {
    return { 
      transport: this.config.get('transport'), 
      options: this.config.get('transportOptions') 
    };
  }
}

@Module({
  imports: [
    ClientsModule.registerAsync([
      {
        name: 'ASYNC_CLIENT',
        imports: [ConfigModule],
        useClass: ClientOptionService,
      },
    ]),
  ],
  controllers: [AppController],
})
export class AppModule {}

Source: app.module.ts

Request-Scoped Providers and Error Handling

Request-scoped handling: For providers requiring request-scoped lifetime, the ListenersController creates unique execution contexts per message. The createRequestScopedHandler method (lines 24-32) generates a distinct ContextId for each incoming message, resolves the provider instance within that context, and ensures proper cleanup after execution.

Error handling: Exceptions thrown within message handlers propagate through ExceptionFiltersContext, mirroring NestJS's HTTP exception filter pipeline. Use RpcException to standardize error serialization across transport boundaries:

import { RpcException } from '@nestjs/microservices';

@MessagePattern({ cmd: 'divide' })
divide(data: { a: number; b: number }) {
  if (data.b === 0) {
    throw new RpcException('Division by zero');
  }
  return data.a / data.b;
}

Summary

  • Bootstrap: Use NestFactory.createMicroservice() with a transport configuration (TCP, Redis, NATS, Kafka, etc.) to instantiate the appropriate Server implementation.
  • Handlers: Decorate controller methods with @MessagePattern (RPC) or @EventPattern (events); the ListenersController.registerPatternHandlers() method wires these to the transport layer.
  • Clients: Inject ClientProxy via @Client() for simple cases or ClientsModule.registerAsync() for dynamic configurations; ClientProxyFactory handles instantiation.
  • Scope: Request-scoped providers are supported via createRequestScopedHandler() with unique ContextId generation per message.
  • Errors: Use RpcException for transport-agnostic error handling processed by ExceptionFiltersContext.

Frequently Asked Questions

What transports does NestJS microservices support?

NestJS supports TCP, Redis, NATS, Kafka, RabbitMQ (RMQ), MQTT, and gRPC through the @nestjs/microservices package. Each transport implements the generic Server interface, allowing you to switch between messaging systems without changing your handler logic.

How do I create a hybrid application that accepts both HTTP and microservice requests?

Use NestFactory.create() to create your standard HTTP application, then call app.connectMicroservice() with your transport configuration. Finally, invoke app.startAllMicroservices() before app.listen() to initialize both servers within the same process.

What is the difference between @MessagePattern and @EventPattern?

@MessagePattern establishes a request-response relationship where the client awaits a reply (RPC), while @EventPattern implements fire-and-forget event distribution where the emitter does not expect a response. The ListenersController registers both types in packages/microservices/listeners-controller.ts.

How do I handle errors in microservice handlers?

Throw RpcException from your handler methods. The ListenersController catches exceptions and passes them through ExceptionFiltersContext, serializing the error for transport back to the client in a format consistent with NestJS's HTTP error handling.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →