How to programmatically exit a Node.js program

Updated: December 30, 2023 By: Guest Contributor Post a comment

Introduction

Node.js applications run on a single-threaded event loop, which manages asynchronous operations and allows for high throughput. However, there are scenarios where you might want to programmatically terminate a Node.js program. This could be due to an error, completion of the required task, or response to specific user input. In this guide, we’ll delve into different methods to gracefully or forcefully exit a Node.js program, while using the latest syntax such as arrow functions, async/await, and ES modules.

Basic Method: process.exit()

The process global object in Node.js provides process.exit() method that is used to terminate the Node.js process. You can call it with an optional exit code:

process.exit(0); // Exits with a 'success' code 
process.exit(1); // Exits with a 'failure' code

Calling process.exit() will force the process to stop quickly, potentially cutting off asynchronous operations and event handling in progress. Typically, an exit code of 0 signifies successful execution, while a non-zero exit code signifies an error or abnormal termination.

Cleanup before Exiting

Sometimes, you might want to perform certain cleanup tasks before the program exits. Node.js provides the 'exit' event, which can be used to run some cleanup code:

process.on('exit', (code) => {
    console.log('Process is exiting with code:', code);
});
process.exit(0);

However, the 'exit' event should only be used to perform synchronous operations because the event loop is no longer being polled at this point: no asynchronous task will be executed.

Using Signals to Exit

You can also use signals to intercept and handle termination requests for a process. For example, listening for SIGTERM and SIGINT:

process.on('SIGTERM', () => {
  console.log('Received SIGTERM, shutting down.');
  process.exit(0);
});

process.on('SIGINT', () => {
  console.log('Received SIGINT, shutting down.');
  process.exit(0);
});

SIGTERM is conventionally used to request a graceful shutdown, whereas SIGINT is generated by pressing Ctrl + C in the terminal. By default, both signals exit the process, but by capturing them, you can perform cleanup before exiting.

Exiting Asynchronously

To run asynchronous operations before exiting, we cannot rely on the 'exit' event. Instead, we should perform asynchronous cleanup in response to a signal or an error and then call process.exit():

const performCleanup = async () => {
    // Replace with actual async cleanup logic
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log('Cleanup completed.');
    process.exit(0);
};

process.on('SIGINT', async () => {
  console.log('Received SIGINT. Cleaning up...');
  await performCleanup();
});

Note that we must invoke process.exit() after performCleanup() to ensure the event loop doesn’t continue running additional events or operations after the cleanup has completed.

Graceful Shutdown in Servers

If your Node.js application is running an HTTP server, it’s important to achieve a graceful shutdown by allowing ongoing requests to complete and refusing new ones. Here is how you can gracefully shutdown an HTTP server:

import http from 'http';

const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('Hello World\n');
});

server.listen(3000, () => {
  console.log('Server running...');
});

const gracefulShutdown = () => {
  server.close(() => {
    console.log('Server is closing...');
    process.exit(0);
  });
  setTimeout(() => {
    console.error('Forcing shutdown...');
    process.exit(1);
  }, 5000); // Force shutdown after 5 seconds
};

process.on('SIGTERM', gracefulShutdown);
process.on('SIGINT', gracefulShutdown);

In the above code, server.close() stops the server from accepting new connections; meanwhile, existing connections are allowed to complete. After calling this method, if the server does not shut down within the specified time frame, a forced shutdown is initiated to prevent the server from hanging indefinitely.

Conclusion

There are various ways to exit a Node.js program, ranging from the abrupt process.exit() method to a more refined strategy involving signals for a graceful shutdown. When exiting your applications, remember to clean up any resources and maintain the stability of your service. Knowing the right tools and using them judiciously minimizes potential disruption or data loss.

By considering the appropriateness of immediate versus graceful exits, acknowledging the need for asynchronous cleanup, and understanding the requirements of running services, you can create robust Node.js applications that gracefully handle shutdown scenarios across different deployments.