Skip to content

Graceful Shutdown in Express

Express/

You don’t pull the power cable to turn off your computer, do you? Then why would you do such thing with your Express application.

A graceful shutdown means letting your app finish its work before it exits. Your app stops serving new requests and finishes working on current ones.

In this post, you will learn how to perform a graceful shutdown in Express using TypeScript. You can also check out the code in this demo repository.

Creating an Exit Handler

To shut down gracefully, start by creating a handler function you can use any time you want to stop your app.

Create a class that implements a method for exiting your app. Start basic and call process.exit to exit your application.

// src/ExitHandler.ts

class ExitHandler {
  public async handleExit(code: number, timeout = 5000): Promise<void> {
    setTimeout(() => {
      console.log(`Forcing a shutdown with code ${code}`);
      process.exit(code);
    }, timeout).unref();

    // Graceful shutdown logic goes here

    process.exit(code);
  }
}

export const exitHandler = new ExitHandler();

Node.js lets you exit your application by calling process.exit and giving it a code, which is a number. Giving it 0 means that your application exited without a problem. Any other number indicates an error.

The interesting part here is the use of setTimeout. It forces your application to exit, if the app fails to do it in the time specified by timeout parameter. It is given in milliseconds, so 5000 means 5 seconds, but you can play around with different values. Depending on how long your shutdown logic takes, you can use a lower number. Or, you might need to go higher, if it takes takes your app longer.

You need to call unref() after using setTimeout. Otherwise, the Node.js process can continue running in the background even after your application exits.

Now, add server.close to your exit handler.

import { server } from '../index';

class ExitHandler {
  public async handleExit(code: number, timeout = 5000): Promise<void> {
    // ...

    if (server.listening) {
      server.close();
    }

    // ...
  }
}

By calling server.close, you stop the server from accepting new connections.

To access the server object, you need to export it when setting up your app.

// index.ts

import http from 'http';
import express from 'express';

const app = express();
export const server = http.createServer(app);

Now you will be able to import server throughout your app, including in your exit handler.

You have stopped your application from accepting new connections, but it doesn’t kill existing connections. Clients may remain connected to your application because of Keep-Alive.

Terminating Existing HTTP Connections

Before you shut down your application, you need to terminate existing connections. You can either implement the logic yourself or use an NPM package dedicated for it. You need to capture all incomming connections and keep track of them. Then you can terminate them before shutting down.

This time you can use http-terminator, but you are free to explore other options.

Install it as dependency.

npm i http-terminator

Go to your app setup and call the createHttpTerminator function, passing it your server object.

// index.ts

import http from 'http';
import express from 'express';
import { createHttpTerminator } from 'http-terminator';

const app = express();
export const server = http.createServer(app);
export const httpTerminator = createHttpTerminator({
  server,
});

You are now ready to terminate existing connections while performing a shutdown.

Revisit your exit handler and use httpTerminator.terminate instead of server.close.

import { httpTerminator, server } from '../index.ts';

class ExitHandler {
  public async handleExit(code: number, timeout = 5000): Promise<void> {
    // ...

    if (server.listening) {
      await httpTerminator.terminate();
    }

    // ...
  }
}

You can add other things to your shutdown procedure. For example, terminating the connection to a database.

import { httpTerminator, server } from '../index.ts';
import { getConnection } from 'typeorm';

class ExitHandler {
  public async handleExit(code: number, timeout = 5000): Promise<void> {
    try {
      console.log(`Attempting a graceful shutdown with code ${code}`);

      setTimeout(() => {
        console.log(`Forcing a shutdown with code ${code}`);
        process.exit(code);
      }, timeout).unref();

      if (server.listening) {
        console.log('Terminating HTTP connections');
        await httpTerminator.terminate();
      }

      const dbConnection = await getConnection();

      if (dbConnection.isConnected) {
        console.log('Closing database connection');
        await dbConnection.close();
      }

      console.log(`Exiting gracefully with code ${code}`);
      process.exit(code);
    } catch (error) {
      console.log('Error shutting down gracefully');
      console.log(error);
      console.log(`Forcing exit with code ${code}`);
      process.exit(code);
    }
  }
}

export const exitHandler = new ExitHandler();

You should also wrap your shutdown procedure in a try/catch. You don’t want your application to fail to shut down because of an error.

The last thing that remains for you to do is to use your exit handler when exiting your application.

Using the Custom Exit Handler

You need to intercept the signals responsible for shutting down your application. Then you can override default exit behavior by calling your exit handler.

Register two event-handling functions on the Node.js process object for SIGTERM and SIGINT events.

// src/process.ts

process.on('SIGTERM', () => {
  console.log(`Process ${process.pid} received SIGTERM: Exiting with code 0`);
  exitHandler.handleExit(0);
});

process.on('SIGINT', () => {
  console.log(`Process ${process.pid} received SIGINT: Exiting with code 0`);
  exitHandler.handleExit(0);
});

Since these signals are received upon a shutdown and not a server crash, use 0 as the exit code.

Don’t forget to import process.ts file when setting up your app in index.ts.

// ... other imports
import './src/process';

// ... setup code

Don’t forget to use your exit handler when handling errors that require your application to restart. In such cases you would call exitHandler.handleExit with any other code that is not 0, such as 1. A non-zero exit code indicates that your app exited due to an error.

I have written a post How to Handle Errors in Express with TypeScript, where I explain when you should exit your app while handling errors.

Summary

In this post you learned how to create your own function for shutting down your application. You learned how to prevent new connections to your server and terminating existing ones. Finally, you learned about intercepting signals that shut down your app and making them use your exit handler instead.

Don’t forget that you can check out the demo repository, which also includes error handling.