DEV Community

Cover image for Gracefully Shutdown Node Apps
Thomas Pegler
Thomas Pegler

Posted on

1

Gracefully Shutdown Node Apps

This article will be primarily written for Dockerized Node apps, but the final stage can definitely be used for any Node apps because it makes the teardown process nicer and more informative.

Getting started

First up is installing an init service, I've personally been using dumb-init from Yelp.

ENTRYPOINT [ "dumb-init", "node", "app.js" ]
Enter fullscreen mode Exit fullscreen mode

With that, you get all signals properly injected into the environment (SIGINT, SIGTERM, SIGHUP etc.) so that you can handle them yourself. This is useful because without them, the process just dies, all connections are terminated and this can leave connected services hanging, data correctness issues and a terrible user experience because they're just kicked.

Here's a simple setup similar to what I use for handling these a bit more gracefully:

import cors from 'cors';
import express, {
    Express, Request, Response,
} from 'express';
import helmet from 'helmet';
import { createServer } from 'http';

/**
 * Main Express src class.
 *
 * Create a new instance and then run with await instance.start().
 */
class Server {
    app: Express;

    config: Config;

    token?: string | null;

    constructor() {
        // Get and set environment variables
        this.app = express();
        this.config = getConfig();
    }


    // eslint-disable-next-line class-methods-use-this
    teardown() {
        disconnectFromDatabase()
          .then( () => console.log( 'Disconnected from Mongo servers.' ) )
          .catch(
            ( e: Error ) => {
              console.error( `Received error ${e.message} when disconnecting from Mongo.` );
            },
          );
    }

    async start() {
        this.app.use( express.urlencoded( { extended: true, limit: this.config.fileSizeLimit } ) );
        this.app.use( express.json( { strict: false, limit: this.config.fileSizeLimit } ) );
        this.app.use( express.text() );
        this.app.use( helmet() );
        this.app.use( cors() );
        this.app.use( limiter );

        const server = createServer( this.app ).listen( this.config.port, () => {
            console.log(
                `🚀 Server ready at ${this.config.host}`,
            );
        } );
        server.timeout = this.config.express.timeout;

        process.on( 'SIGINT', () => {
            console.log( 'Received SIGINT, shutting down...' );
            this.teardown();
            server.close();
        } );
        process.on( 'SIGTERM', () => {
            console.log( 'Received SIGTERM, shutting down...' );
            this.teardown();
            server.close();
        } );
        process.on( 'SIGHUP', () => {
            console.log( 'Received SIGHUP, shutting down...' );
            this.teardown();
            server.close();
        } );
    }
}

const server = new Server();

server.start()
    .then( () => console.log( 'Running...' ) )
    .catch( ( err: Error | string ) => {
        console.error( err );
    } );

Enter fullscreen mode Exit fullscreen mode

That's essentially it. I didn't include a few of the methods but if you connect to a database or AMQP service or some other service, it's good to properly close those connections off to ensure that any read/write/push/whatever is completed before forcefully stopping the application.


Header by Simon Infanger on Unsplash

Image of Datadog

Create and maintain end-to-end frontend tests

Learn best practices on creating frontend tests, testing on-premise apps, integrating tests into your CI/CD pipeline, and using Datadog’s testing tunnel.

Download The Guide

Top comments (0)

The Most Contextual AI Development Assistant

Pieces.app image

Our centralized storage agent works on-device, unifying various developer tools to proactively capture and enrich useful materials, streamline collaboration, and solve complex problems through a contextual understanding of your unique workflow.

👥 Ideal for solo developers, teams, and cross-company projects

Learn more