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
| Aspect | Built-in NATS | nestjs-jetstream |
|---|---|---|
| Delivery | At-most-once (fire-and-forget) | At-least-once (persistent) |
| Retention | None — messages lost if no subscriber | Stream-based — messages survive restarts |
| Replay | Not supported | New consumers catch up on history |
| Fan-out | All subscribers get every message | Workqueue (one handler) or Broadcast (all) |
| RPC | Core request/reply | Core (default) or JetStream-backed |
| DLQ | Not supported | onDeadLetter callback after N failures |
| Ack/Nak | Not applicable | Explicit 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_schedulesstream config option
v2.6 → v2.7
New features:
- Handler-controlled settlement via
ctx.retry()andctx.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,DeliverPolicyoptions) - 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
onDeadLettercallback DeadLetterInfointerface with full message context
No breaking changes.
See also
- Installation — setup requirements
- Module Configuration — full options reference
- Quick Start — first handler in 5 minutes