Table of Contents
Introduction
Shutting down a NestJS application programmatically involves understanding the Nest lifecycle and the mechanisms for graceful termination. Whether for maintenance or controlled environment updates, implementing a proper shutdown sequence is key for robust applications.
Initializing a Basic NestJS Application
Before diving into shutdown procedures, let’s set up a basic NestJS application. Install Node.js if it isn’t already installed and then run the following commands:
$ npm i -g @nestjs/cli
$ nest new my-nest-app
After the installation completes, enter the project directory and start your application:
$ cd my-nest-app
$ npm run start
With our server running, let’s see how we can programmatically shut it down.
Basic Shutdown with a Signal Trap
To shut down your application, you might want to catch system signals and respond accordingly. Add the following to your main.ts
file:
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
process.on('SIGINT', () => {
app.close().then(() => {
console.log('Application terminated');
process.exit(0);
});
});
}
bootstrap();
This sets up a listener for the SIGINT signal, which is typically triggered by pressing Ctrl + C
in the terminal. When this signal is received, the app will attempt to close cleanly before the process exits.
Graceful Shutdown with Asynchronous Cleanup
Sometimes, your application needs to perform tasks like closing database connections or flushing logs before actually shutting down. Modify your bootstrap
function to handle this:
async function gracefulShutdown(app) {
try {
// Perform your cleanup actions
await app.close();
console.log('Cleanup finished. Shutting down.');
} catch (error) {
console.error('Error during shutdown', error);
} finally {
process.exit(0);
}
}
async function bootstrap() {
const app = await NestFactory.create(AppModule);
await app.listen(3000);
process.on('SIGTERM', () => {
gracefulShutdown(app);
});
}
bootstrap();
Now you’re catching SIGTERM, which is commonly used for a more immediate shutdown, and invoking the gracefulShutdown
function that takes care of additional operations before closing.
Advanced: Integrating Lifecycle Hooks
NestJS offers lifecycle hooks for finer control over the application’s behavior on shutdown. Implementing the OnModuleDestroy
hook can help manage shutdown routines at the module level:
import { OnModuleDestroy } from '@nestjs/common';
@Module({ /* ... */ })
export class AppModule implements OnModuleDestroy {
async onModuleDestroy() {
// Handle cleanup logic here
console.log('Module cleanup...');
}
}
Showcasing this in your main application:
async function bootstrap() {
const app = await NestFactory.createApplicationContext(AppModule);
process.on('SIGTERM', () => {
app.close().then(() => {
console.log('Application shutdown after module cleanup');
process.exit(0);
});
});
}
bootstrap();
This ApplicationContext
variant allows you to manage the lifecycle independently from the HTTP server, making it useful for applications that aren’t solely serving web requests. Use the typical NestFactory.create()
for web server lifecycles along with the OnModuleDestroy
hook.
Handling Shutdown in Microservices
NestJS Microservices have their nuances when shutting down. When working with a hybrid app, you should consider each transport separately:
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const microservice = app.connectMicroservice<MicroserviceOptions>({
transport: Transport.TCP,
});
await app.startAllMicroservices();
await app.listen(3000);
process.on('SIGTERM', async () => {
console.log('Microservice is shutting down...');
await microservice.close();
gracefulShutdown(app);
});
}
bootstrap();
Ensure that you close the microservice connections to have a clean shutdown of your entire NestJS environment.
Crafting a Custom Shutdown Hook
If the built-in hooks do not satisfy your requirements, you can craft your own by leveraging custom providers:
@Injectable()
class ShutdownService {
constructor(@Inject(APP_INTERCEPTOR) private readonly logger: LoggerService) {}
async onApplicationShutdown(signal?: string) {
// Logging shutdown signal and performing cleanup tasks
this.logger.log(`Received shutdown signal: ${signal}`);
// Perform your custom cleanup here
}
}
// Use this service in your main.bootstrap function
With the ShutdownService
, you gain more control over what actions are executed when a shutdown signal is received.
Testing Programmatically Initiated Shutdowns
Testing is critical, even for shutdown sequences. Trigger a shutdown programmatically in your test cases like so:
// Test setup...
it('programmatically shuts down the application', async () => {
const app = await testInstance.createTestingModule({ /* ... */ }).compile().createNestApplication();
await app.init();
const shutdownSpy = jest.spyOn(app, 'close');
process.kill(process.pid, 'SIGTERM');
expect(shutdownSpy).toBeCalled();
});
This ensures your shutdown logic is functioning as expected, and you can rely on it to release resources and prettify exiting in production environments.
Conclusion
Programmatically shutting down a NestJS application requires understanding the system signals and the internal lifecycle hooks that the framework provides. Whether you’re working on maintenance or ensuring clean disconnections during updates and deploys, knowing how to gracefully manage your application’s termination helps maintain data integrity and system stability. Following the strategies outlined in this article, you should be able to control your NestJS shutdown process, from simple signal trapping to bespoke cleanup logic.