Graceful Shutdown
The transport handles shutdown automatically through the NestJS application lifecycle. When the application shuts down, in-flight message handlers are given time to complete before the NATS connection is closed. No manual shutdown code is needed.
How it works
The JetstreamModule implements NestJS's OnApplicationShutdown interface. When the application receives a termination signal (SIGTERM, SIGINT), NestJS calls the module's onApplicationShutdown() method, which triggers the following sequence:
- Emit
ShutdownStarthook — notifies lifecycle hooks that shutdown has begun. - Stop consumers — calls
strategy.close(), which closes all RxJS subscriptions and stops JetStream consumer iterators. No new messages are accepted. In-flight handlers in the RxJS pipeline continue to run to completion. - Drain NATS connection — calls
nc.drain(), which flushes any pending publishes and waits for active subscriptions to finish, then closes the connection. - Safety timeout — if drain doesn't complete within
shutdownTimeoutmilliseconds, the transport proceeds with shutdown anyway. This prevents a stuck handler from blocking the process indefinitely. - Emit
ShutdownCompletehook — notifies lifecycle hooks that shutdown is finished.
What "drain" means
NATS drain() is a graceful shutdown primitive. When you drain a connection:
- The client stops receiving new messages from all subscriptions.
- Messages already delivered to handlers continue processing normally.
- The client waits for all pending message handlers to complete and send their ack/nak.
- Once all in-flight work is done, the connection closes cleanly.
This ensures that messages are not lost or left in an ambiguous state. A message that was being processed when shutdown started will either be acknowledged (if the handler succeeds) or nak'd (if it fails), so NATS can redeliver it to another instance.
Configuring the timeout
The shutdownTimeout option controls how long the transport waits for the drain to complete. The default is 10 seconds (10,000 ms).
import { Module } from '@nestjs/common';
import { JetstreamModule } from '@horizon-republic/nestjs-jetstream';
@Module({
imports: [
JetstreamModule.forRoot({
name: 'orders',
servers: ['nats://localhost:4222'],
shutdownTimeout: 30_000, // 30 seconds for long-running handlers
}),
],
})
export class AppModule {}
Set shutdownTimeout to slightly more than your longest handler's expected duration. If your slowest handler takes 20 seconds, a timeout of 25-30 seconds gives it room to finish. Too short, and handlers may be abandoned mid-execution; too long, and your deployment pipeline will wait unnecessarily.
If the timeout fires before drain completes, the transport closes the connection immediately. Any in-flight handlers that haven't finished will be interrupted — their messages will not be acknowledged, so NATS will redeliver them to another instance after the consumer's ack_wait expires.
Enabling shutdown hooks
For the shutdown sequence to trigger, NestJS must be configured to listen for OS signals. Call enableShutdownHooks() in your bootstrap function:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// Required for graceful shutdown to work
app.enableShutdownHooks();
await app.listen(3000);
}
void bootstrap();
If you skip enableShutdownHooks(), the process will terminate immediately on SIGTERM/SIGINT without calling onApplicationShutdown(). The NATS connection will drop abruptly, and in-flight messages will be redelivered after ack_wait expires — which works, but is not clean.
No manual shutdown code needed
Unlike some transports that require you to manage connection lifecycle manually, nestjs-jetstream handles everything through the NestJS module lifecycle:
- Startup:
forRoot()creates the connection, streams, and consumers. - Shutdown:
onApplicationShutdown()drains the connection and cleans up.
You don't need to call app.close() in a signal handler, inject the connection to drain it manually, or implement your own shutdown manager. The transport takes care of it.
Observing shutdown with hooks
Use ShutdownStart and ShutdownComplete lifecycle hooks for logging or metrics:
import { JetstreamModule, TransportEvent } from '@horizon-republic/nestjs-jetstream';
JetstreamModule.forRoot({
name: 'orders',
servers: ['nats://localhost:4222'],
hooks: {
[TransportEvent.ShutdownStart]: () => {
console.log('NATS transport shutting down...');
},
[TransportEvent.ShutdownComplete]: () => {
console.log('NATS transport shutdown complete');
},
},
})
See Lifecycle Hooks for all available events.
What's next?
- Lifecycle Hooks — all 9 transport lifecycle events
- Health Checks — monitor connection status
- Module Configuration —
shutdownTimeoutand other options