Skip to content
NestJS ns microservices 4 min read

NATS Transport

NATS is a lightweight, high-performance messaging system built around the publish/subscribe model and subject-based routing. NestJS ships a first-class NATS transporter that maps message patterns to NATS subjects, supports load balancing through queue groups, and handles request-reply transparently so your @MessagePattern handlers feel exactly like any other microservice. This makes NATS an excellent choice when you want low latency, simple operations, and built-in fan-out without standing up a heavier broker.

How NestJS maps to NATS

In NestJS, every message pattern you declare becomes a NATS subject. When a client sends client.send('user.created', payload), Nest publishes a message on the user.created subject and a microservice subscribed with @MessagePattern('user.created') receives it. Patterns can be strings or objects — object patterns are serialized into a stable subject string, so { cmd: 'sum' } and 'user.created' are both valid.

NATS distinguishes two interaction styles, and Nest supports both:

StyleNestJS APINATS behavior
Request-replyclient.send() / @MessagePattern()Uses NATS request with an auto-generated reply inbox
Fire-and-forgetclient.emit() / @EventPattern()Plain publish, no response expected

Installing and configuring the transporter

First install the NATS client library used by the transporter.

npm install nats

Configure a microservice to listen over NATS in main.ts. The transporter connects to one or more NATS servers and begins subscribing to the subjects derived from your handlers.

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

async function bootstrap() {
  const app = await NestFactory.createMicroservice<MicroserviceOptions>(
    AppModule,
    {
      transport: Transport.NATS,
      options: {
        servers: ['nats://localhost:4222'],
        queue: 'users-service',
      },
    },
  );

  await app.listen();
}
bootstrap();

Output:

[Nest] 4821  - 06/14/2026, 10:02:11 AM   LOG [NestFactory] Starting Nest application...
[Nest] 4821  - 06/14/2026, 10:02:11 AM   LOG [NestMicroservice] Nest microservice successfully started

Common connection options

The options object is passed through to the underlying NATS client, so any native connection setting is available alongside the Nest-specific queue.

OptionTypeDescription
serversstring | string[]One or more nats://host:port URLs for the cluster
queuestringQueue group name for load-balanced subscriptions
namestringClient name shown in NATS monitoring
token / user / passstringAuthentication credentials
reconnectbooleanAutomatically reconnect on disconnect (default true)
maxReconnectAttemptsnumberReconnect attempt limit; -1 for unlimited

Subject-based routing

Handlers subscribe to subjects through the standard message and event decorators. Use @MessagePattern when the caller expects a response and @EventPattern for one-way notifications.

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

interface CreateUserDto {
  email: string;
  name: string;
}

@Controller()
export class UsersController {
  @MessagePattern('user.find')
  findUser(@Payload() id: string) {
    return { id, name: 'Ada Lovelace', email: '[email protected]' };
  }

  @EventPattern('user.created')
  handleUserCreated(@Payload() data: CreateUserDto) {
    console.log(`Provisioning resources for ${data.email}`);
  }
}

NATS subjects support wildcards (* for a single token, > for multiple). Because Nest derives the subject from the pattern, hierarchical names like 'order.payment.completed' let you organize traffic cleanly while keeping handlers explicit.

Queue groups for load balancing

A queue group lets multiple instances of the same service share a subscription so that each message is delivered to exactly one member of the group. This is how you scale horizontally: run three replicas of users-service with the same queue value, and NATS round-robins requests across them. Without a queue group, every subscriber receives every message — useful for broadcast events, but not for work distribution.

options: {
  servers: ['nats://localhost:4222'],
  queue: 'users-service', // all replicas in this group split the load
}

Set the same queue on all replicas of a service. If you omit it, every instance will independently handle the same @MessagePattern, causing duplicate processing and racing replies.

Request-reply from a client

Inject a ClientProxy to call other services. send() returns an Observable that completes with the handler’s response; emit() fires an event and returns immediately.

// users.module.ts
import { Module } from '@nestjs/common';
import { ClientsModule, Transport } from '@nestjs/microservices';
import { GatewayController } from './gateway.controller';

@Module({
  imports: [
    ClientsModule.register([
      {
        name: 'USERS_CLIENT',
        transport: Transport.NATS,
        options: { servers: ['nats://localhost:4222'] },
      },
    ]),
  ],
  controllers: [GatewayController],
})
export class UsersModule {}
// gateway.controller.ts
import { Controller, Get, Inject, Param } from '@nestjs/common';
import { ClientProxy } from '@nestjs/microservices';
import { firstValueFrom } from 'rxjs';

@Controller('users')
export class GatewayController {
  constructor(@Inject('USERS_CLIENT') private readonly client: ClientProxy) {}

  @Get(':id')
  async getUser(@Param('id') id: string) {
    return firstValueFrom(this.client.send('user.find', id));
  }
}

Output:

$ curl http://localhost:3000/users/42
{"id":"42","name":"Ada Lovelace","email":"[email protected]"}

Behind the scenes Nest publishes the request on the user.find subject with a temporary reply inbox, the listening service responds to that inbox, and the Observable emits the decoded payload.

Best practices

  • Use a consistent queue group name per logical service so replicas load-balance instead of duplicating work.
  • Reserve @EventPattern / emit() for fan-out notifications and @MessagePattern / send() for calls that need a reply.
  • Adopt a hierarchical subject naming convention (domain.entity.action) to keep routing readable and wildcard-friendly.
  • Provide multiple servers entries and leave reconnect enabled so clients survive node failures in a NATS cluster.
  • Always set a request timeout on the caller side (via RxJS timeout()) so a missing responder fails fast instead of hanging.
  • Keep payloads small and serializable; NATS is optimized for high message throughput, not large blobs.
Last updated June 14, 2026
Was this helpful?