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:
MetadataScannerinspects controller metadataListenerMetadataExploreridentifies decorated methodsregisterPatternHandlers()(lines 61-78) binds patterns to the underlyingServerinstance
// 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 appropriateServerimplementation. - Handlers: Decorate controller methods with
@MessagePattern(RPC) or@EventPattern(events); theListenersController.registerPatternHandlers()method wires these to the transport layer. - Clients: Inject
ClientProxyvia@Client()for simple cases orClientsModule.registerAsync()for dynamic configurations;ClientProxyFactoryhandles instantiation. - Scope: Request-scoped providers are supported via
createRequestScopedHandler()with uniqueContextIdgeneration per message. - Errors: Use
RpcExceptionfor transport-agnostic error handling processed byExceptionFiltersContext.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →