Skip to main content

Migration Guide

From @nestjs/microservices NATS transport

The built-in NestJS NATS transport uses Core NATS — fire-and-forget pub/sub with no persistence. This library adds JetStream on top, providing durable delivery, retry, and replay.

What changes

AspectBuilt-in NATSnestjs-jetstream
DeliveryAt-most-once (fire-and-forget)At-least-once (persistent)
RetentionNone — messages lost if no subscriberStream-based — messages survive restarts
ReplayNot supportedNew consumers catch up on history
Fan-outAll subscribers get every messageWorkqueue (one handler) or Broadcast (all)
RPCCore request/replyCore (default) or JetStream-backed
DLQNot supportedonDeadLetter callback after N failures
Ack/NakNot applicableExplicit acknowledgment per message

Step 1 — Install the library

pnpm add @horizon-republic/nestjs-jetstream

Step 2 — Replace module registration

Before (built-in):

// main.ts
app.connectMicroservice({
transport: Transport.NATS,
options: { servers: ['nats://localhost:4222'] },
});

After (nestjs-jetstream):

// app.module.ts
@Module({
imports: [
JetstreamModule.forRoot({
name: 'my-service',
servers: ['nats://localhost:4222'],
}),
],
})
export class AppModule {}

Then connect the transport in your bootstrap file, just like in the Quick Start:

const app = await NestFactory.create(AppModule);
app.connectMicroservice({ strategy: app.get(JetstreamStrategy) }, { inheritAppConfig: true });
await app.startAllMicroservices();

Step 3 — Keep your handlers

Your existing @EventPattern() and @MessagePattern() handlers work as-is. The decorators are the same — only the underlying transport changes.

// Works with both transports — no code changes needed
@EventPattern('user.created')
async handleUserCreated(@Payload() data: UserDto) {
await this.userService.process(data);
}

@MessagePattern('user.get')
async getUser(@Payload() id: string) {
return this.userService.findById(id);
}

Step 4 — Replace client injection

Before (built-in):

@Inject('NATS_SERVICE') private readonly client: ClientProxy

After (nestjs-jetstream):

// Register in the module
JetstreamModule.forFeature({ name: 'users' })

// Inject with the service name
@Inject(getClientToken('users')) private readonly client: JetstreamClient

Step 5 — Adjust for acknowledgment semantics

The key behavioral difference: messages are now acknowledged explicitly. If your handler throws, the message is retried (up to max_deliver times, default 3).

Idempotency matters now. If a handler is called twice with the same message, the second call should produce the same result. Use message deduplication or idempotent operations.

What you gain

After migration, you get for free:

  • Messages survive NATS server restarts
  • Failed messages are automatically retried
  • Dead letter handling for exhausted retries
  • Health checks with RTT monitoring
  • Graceful shutdown with message drain
  • Broadcast fan-out to all service instances
  • Ordered sequential delivery mode

Upgrading between versions

v2.7 → v2.8

Breaking change: migrated from nats package to @nats-io/* scoped packages (v3.x).

This is an internal change — the library re-exports everything users need. If you import types directly from nats in your own code, update them:

- import { JsMsg, NatsConnection } from 'nats';
+ import { JsMsg } from '@nats-io/jetstream';
+ import { NatsConnection } from '@nats-io/transport-node';

New features:

  • Message scheduling — one-shot delayed delivery via scheduleAt() (requires NATS >= 2.12)
  • allow_msg_schedules stream config option

v2.6 → v2.7

New features:

  • Handler-controlled settlement via ctx.retry() and ctx.terminate() — control message acknowledgment without throwing errors
  • Metadata getters on RpcContext: getDeliveryCount(), getStream(), getSequence(), getTimestamp(), getCallerName()

No breaking changes.

v2.5 → v2.6

New features:

  • Configurable concurrency for event/broadcast/RPC processing
  • Ack extension (ackExtension: true) for long-running handlers
  • Consume options passthrough for advanced prefetch tuning
  • Heartbeat monitoring with automatic consumer restart
  • S2 stream compression enabled by default
  • Performance connection defaults (unlimited reconnect, 1s interval)

No breaking changes.

v2.4 → v2.5

Breaking change: nanos() renamed to toNanos().

- import { nanos } from '@horizon-republic/nestjs-jetstream';
+ import { toNanos } from '@horizon-republic/nestjs-jetstream';

consumer: {
- ack_wait: nanos(30, 'seconds'),
+ ack_wait: toNanos(30, 'seconds'),
}

v2.3 → v2.4

New features:

  • Ordered events (ordered: prefix, DeliverPolicy options)
  • Custom message IDs via setMessageId() for publish-side deduplication
  • Documentation site (Docusaurus)

No breaking changes.

v2.1 → v2.2

New features:

  • Dead letter queue support via onDeadLetter callback
  • DeadLetterInfo interface with full message context

No breaking changes.

See also