node.jsdockerexpressgraceful-shutdown

How to gracefully shutdown or close Node.Js Express app


Motivation

I want to ensure there is no brute (sub-)process termination and that any open requests are fulfilled when the Express server is closing.

import express, { Express, Request, Response } from 'express';

// Initialize the Express server application
var app = express();
var server = this.app.listen(process.env.PORT || 5000, () => { ... } );

// POST endpoint to process the shopping cart
app.post('/purchase', async (req, res) => {
  // Get the items from the request body

  try {
      const { buyer, items } = req.body;
      
      // Process the items (e.g., calculate total price, initiate payment, etc.)
      const totalPrice = calculateTotalPrice(items);
      // Charge the buyer for the purchase
      const payment = chargeBuyerForPurchase(totalPrice);
      
      // đŸĨˇ I don't want the server to close before it executes this line for every request

      // Create shipping for the purchased items
      const shipping = carrierAPI.createShipping(items, buyer.address);

      res.status(200).json([totalPrice, payment, shipping]);

      // 💁‍♂ī¸ This is now safe to close the server
    } catch (error) {
      next(error);
    }

});

Proposed Solution

See documentation at this link

// ./src/app.ts
import process from 'node:process';

// Begin reading from stdin so the process does not exit.
process.stdin.resume();

process.on('SIGINT', () => {
  console.log('Received SIGINT. Press Control-D to exit.');
});

// FYI docker "stop" <container>, a process manager, and most hosts will send SIGTERM signal when it is shutting down.
// server.close stops the server from accepting new connections and closes all connections connected to this server that are not sending a request or waiting for a response

function gracefulClose(signal) {
  console.log(`Received ${signal}`);
  server.close( () => { log('HTTP(S) server closed') } );

}

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

Is it a feature to implement? Is this feature / code redundant?


Solution

  • THESE ARE MY FINDINGS:

    1. When the SIGTERM signal is emitted, the Node.JS server will be immediately closed. The Node.Js application / process will still be alive. Moreover, a response will not be sent to any client even if its connection was open before the SIGTERM signal was received.

    2. When the SIGINT signal is emitted, the Node.JS (Express) server and application or process will be immediately closed.

    3. When a custom signal is emitted, SIGUSR1 or SIGUSR2, the Node.JS server will immediately close any open connections. The Node.Js application / process will still be alive. The Node.Js (Express) server will still be alive too and fulfill only new requests and responses.

    PS: Signals are software interrupts sent to a program to indicate that an important event has occurred. Another common method for delivering signals is to use the kill command, the syntax of which is as follows −

    $ kill -signal <pid>
    

    MY SOLUTION:

    I have created an npm package to [super-gracefully-shutdown][1] your Node.Js Express server. It ensures a response is sent to every client for any connections that were open before you send a super-graceful `shutdown` message.


    Usage / Implementation

    import express from 'express';
    import SGS from 'super-graceful-shutdown';
    
    const app = express();
    const router = express.Router();
    
    const express = require('express');
    const SGS = require('super-graceful-shutdown');
    
    const app = express();
    const router = express.Router();
    const port = 80; // Set your desired port number
    
    // ...
    
    router.get('/api', async function(req, res, next){
      try {
          // Fulfill this request in 3 seconds
          setTimeout(() => { res.status(200).send(`<html> <body> <h1>${date()}</h1> </body> </html>`)  }, 3000);
        } catch (error) {
          next(error);
        }
    });
    
    const server = app.listen(port, () => console.log(`Example Express app listening on port ${port}!`) );
    
    // ℹī¸ Before you initialize other routes, you need to initialize super-graceful-shutdown
    new SGS(app, server);
    
    // 👨‍đŸ’ģ Then, you can initialize other routes
    app.use('/', router);
    

    Request From Host To Shutdown Node.Js Express App

    To super-gracefully-shutdown your Node.Js Express application, send the TCP shutdown message on port 3000:

    echo "shutdown" | nc localhost 3000