Microservices — TCP, Redis, NATS

Connect Nest Services Without HTTP

Microservices — TCP, Redis, NATS

Nest supports several transports for service-to-service communication via `@nestjs/microservices`. Same code, different wire format.

4 min read Level 3/5 #nestjs#microservices
What you'll learn
  • Create a microservice with NestFactory.createMicroservice
  • Send messages with ClientProxy
  • Pick a transport — TCP, Redis, NATS, RabbitMQ

When one Nest service needs to call another, you can use HTTP — but @nestjs/microservices offers transports that are faster, simpler, or better suited to async messaging. The code looks almost identical to a regular controller; only the bootstrap and the client change.

Install

npm i @nestjs/microservices

A Microservice Server

Instead of NestFactory.create, use createMicroservice and pick a transport. TCP is the simplest — no broker required.

import { NestFactory } from '@nestjs/core';
import { Transport, MicroserviceOptions } from '@nestjs/microservices';

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

Inside the module, handlers are decorated with @MessagePattern (for request/response) or @EventPattern (for fire-and-forget).

import { Controller } from '@nestjs/common';
import { MessagePattern, EventPattern, Payload } from '@nestjs/microservices';

@Controller()
export class UsersMessageController {
  @MessagePattern('user.findOne')
  findOne(@Payload() id: number) {
    return this.users.findById(id);
  }

  @EventPattern('user.created')
  onCreated(@Payload() user: { id: number; email: string }) {
    // side effects — send welcome email, etc.
  }
}

The difference: @MessagePattern expects a response, @EventPattern does not. Use events for “this happened” notifications.

The Client Side

The calling service registers a ClientsModule and injects a ClientProxy.

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'USERS_SERVICE',
        transport: Transport.TCP,
        options: { host: 'users', port: 4000 },
      },
    ]),
  ],
})
export class GatewayModule {}

@Injectable()
export class GatewayService {
  constructor(@Inject('USERS_SERVICE') private readonly client: ClientProxy) {}

  findUser(id: number) {
    return firstValueFrom(this.client.send('user.findOne', id));
  }

  notify(user: { id: number; email: string }) {
    this.client.emit('user.created', user);
  }
}

send returns an Observable; wrap it with firstValueFrom (from rxjs) when you want a Promise. emit is fire-and-forget — no response to wait for.

Picking a Transport

  • TCP — simplest, no dependencies. Good for two services in the same cluster.
  • Redis — pub/sub via Redis. Easy if you already run Redis. Great for events.
  • NATS — purpose-built messaging. Fast, lightweight, supports pub/sub and request/reply.
  • RabbitMQ / Kafka — full message brokers. Durable queues, work distribution, replay. Pick these when you need real reliability guarantees.

The handler code doesn’t change between transports — only the bootstrap and the client config. Start with TCP, swap when you outgrow it.

gRPC Microservices →