๐Ÿ’ก๐—›๐—ฎ๐—ป๐—ฑ๐—น๐—ถ๐—ป๐—ด ๐—š๐—น๐—ผ๐—ฏ๐—ฎ๐—น ๐—˜๐˜…๐—ฐ๐—ฒ๐—ฝ๐˜๐—ถ๐—ผ๐—ป๐˜€ & ๐—š๐—ฟ๐—ฎ๐—ฐ๐—ฒ๐—ณ๐˜‚๐—น ๐—ฆ๐—ต๐˜‚๐˜๐—ฑ๐—ผ๐˜„๐—ป๐˜€ ๐—ถ๐—ป ๐—ง๐˜†๐—ฝ๐—ฒ๐—ฆ๐—ฐ๐—ฟ๐—ถ๐—ฝ๐˜ ๐˜„๐—ถ๐˜๐—ต ๐—ก๐—ฒ๐˜€๐˜๐—๐—ฆ ๐Ÿš€

Apurv upadhyay
3 min readOct 12, 2024

--

As developers, weโ€™ve all faced the frustrating issue of an app unexpectedly crashing and leaving us to dig through logs to figure out what went wrong. Recently, I encountered this exact problem โ€” my app would suddenly stop, causing downtime and impacting users. After implementing global exception handling in my TypeScript-based NestJS application, I was able to handle crashes gracefully and automatically restart the app after logging the error. ๐Ÿ™Œ

Let me walk you through how I tackled this issue by using process.on to catch exceptions and signals, and restart the app if necessary.

๐Ÿ› ๏ธ The Code

import { NestFactory } from "@nestjs/core";
import { AppModule } from "./app.module";
import { Logger } from "@nestjs/common";

async function bootstrap() {
const logger = new Logger("Global Exception Handling Service");

// Handle uncaught exceptions (sync errors not handled in your app)
process.on("uncaughtException", (error: any, origin: any) => {
logger.error({
module: "main process",
method: "uncaughtException",
correlationId: "To be implemented",
timestamp: new Date().toISOString(),
error: error?.message,
stack: error?.stack,
origin: origin,
});
// You may restart the app here
});

// Handle unhandled promise rejections (async errors not caught)
process.on("unhandledRejection", (err: any) => {
logger.error({
module: "main process",
method: "unhandledRejection",
correlationId: "To be implemented",
timestamp: new Date().toISOString(),
error: err?.message,
});
});

// Catch exit events (when the app is about to shut down)
process.on("exit", (code) => {
logger.error({
module: "main process",
method: "exit",
correlationId: "To be implemented",
timestamp: new Date().toISOString(),
error: code,
});
});

// Handle graceful shutdown on SIGTERM (used by cloud providers)
process.on("SIGTERM", async () => {
logger.log("Received SIGTERM. Shutting down gracefully...");
await app.close();
process.exit(0);
});

// Handle graceful shutdown on SIGINT (when user interrupts the process)
process.on("SIGINT", async () => {
logger.log("Received SIGINT. Shutting down gracefully...");
await app.close();
process.exit(0);
});

const app = await NestFactory.create(AppModule);
const appPort = parseInt(process?.env?.APP_PORT) || 3000;
await app.listen(appPort);
}

bootstrap();

๐Ÿ” Breaking It Down

1. process.on(โ€œuncaughtExceptionโ€):

Catches synchronous errors that werenโ€™t caught elsewhere. These are typically errors that escape the try-catch blocks or unhandled errors in your app. Logging such exceptions allows you to identify the root cause and ensure the app can recover.

2. process.on(โ€œunhandledRejectionโ€):

Deals with asynchronous errors โ€” promise rejections that werenโ€™t handled. Itโ€™s crucial to log these to prevent hidden issues from slipping through the cracks.

3. process.on(โ€œexitโ€):

This event gets triggered when the Node.js process is about to exit. Useful for logging any final state or doing cleanup tasks before the app closes completely.

4. process.on(โ€œSIGTERMโ€):

Used by many cloud providers (like AWS, Heroku) to gracefully terminate an app. When receiving this signal, we can close the app gracefully, ensuring all operations complete properly.

5. process.on(โ€œSIGINTโ€):

This is fired when a user presses Ctrl+C in the terminal to stop the application. Similar to SIGTERM, we handle this gracefully, ensuring the app shuts down without data loss.

๐Ÿ’ก Why This Matters?

Before implementing this, my app would just stop without notice. Now, every exception or signal is logged, and I can automatically restart the app after a crash, reducing downtime and improving user experience.

The next time you encounter a random app shutdown, youโ€™ll have all the details logged โ€” no more digging through endless logs trying to figure out what went wrong!

๐Ÿ’ช Takeaway:

Handling exceptions gracefully and ensuring a smooth shutdown process is critical for any production app. By implementing process.on, you:

โ€ข Catch and log all errors (both sync and async).

โ€ข Handle shutdown signals gracefully.

โ€ข Restart the app or perform necessary cleanups when unexpected issues occur.

๐Ÿš€ Using ๐—œ๐—ป๐—น๐—ถ๐—ป๐—ฒ ๐—”๐—ฟ๐—ฟ๐—ฎ๐˜†๐˜€ and ๐—–๐—ผ๐—น๐—น๐—ฒ๐—ฐ๐˜๐—ถ๐—ผ๐—ป ๐—˜๐˜…๐—ฝ๐—ฟ๐—ฒ๐˜€๐˜€๐—ถ๐—ผ๐—ป๐˜€ in C# 12 simplifies your code and boosts performance. Start leveraging these features in your projects to write cleaner, faster, and more readable code!

Follow Apurv Upadhyay โ˜๏ธ for more insightful content..

#TypeScript #NestJS #NodeJS #ErrorHandling #GracefulShutdown #Programming

--

--

Apurv upadhyay
Apurv upadhyay

Written by Apurv upadhyay

Principal Software Engineer at PeerIslands โ€ข Microsoft Azure Certified Architect Expert & DevOps Specialist โ€ข 7x Azure Certified โ€ข ex-Microsoft, Bosch

No responses yet