1.1.0 • Published 1 year ago

@saasquatch/express-boilerplate v1.1.0

Weekly downloads
-
License
MIT
Repository
github
Last release
1 year ago

The SaaSquatch exporess boilerplate package provides a number of middleware and utility functions that are useful in Express apps, particularly those running in Kubernetes environments.

Getting Started

Install the package:

npm install @saasquatch/express-boilerplate

Basic usage:

import {
  healthCheck,
  installShutdownManager,
  requestIdAndLogger,
  shutdownManagerConfigFromEnv,
} from "@saasquatch/express-boilerplate";

import { httpLogMiddleware, initializeLogger } from "@saasquatch/logger";
import express from "express";

const app = express();
const baseLogger = initializeLogger();

app.use(requestIdAndLogger(baseLogger));
app.use(httpLogMiddleware(baseLogger));
app.get("/healthz", healthCheck(app, baseLogger));

const server = installShutdownManager(
  app,
  baseLogger,
  shutdownManagerConfigFromEnv(),
);

server.listen(3000, () => baseLogger.notice("App listening on port 3000"));

Graceful Shutdown Manager

The installShutdownManager function installs the OS signal handlers for SIGINT and SIGTERM to shutdown the server in a graceful way that is compatible with Kubernetes best practices. It is also used to configure the TCP Keep-Alive timeouts for the server, which may be necessary for certain cloud load balancers.

The shutdownManagerConfigFromEnv() function will load the following config options from environment variables:

Environment variableConfig optionMeaning
SSQT_HTTP_KEEP_ALIVE_SECONDSkeepAliveTimeoutSecondsThe number of seconds of inactivity a server needs to wait for additional incoming data, after it has finished writing the last response, before a socket will be destroyed.
SSQT_TERMINATION_DELAY_SECONDSterminationDelaySecondsThe number of seconds to wait after the shutdown signal is received and before exiting the application process.

The options can also be customized directly:

const server = installShutdownManager(app, logger, {
  keepAliveTimeoutSeconds: 620,
  terminationDelaySeconds: 20,
});

server.listen(3000, () => logger.notice("App listening on port 3000"));

The shutdown manager also supports executing hook functions when certain events occur during the shutdown process:

const server = installShutdownManager(app, logger, {
  ...shutdownManagerConfigFromEnv(),

  onSignalReceived: async (signal) =>
    console.log(`"${signal}" signal received!`),

  beforeShutdown: async () => console.log(`HTTP server is about to shut down`),

  afterShutdown: async () => console.log(`HTTP Server has shut down`),
});

server.listen(3000, () => logger.notice("App listening on port 3000"));

RequestId and Logger Middleware

The requestIdAndLogger middleware function generates a random request ID using nanoid and sets the res.locals.requestId and res.locals.logger variables. These can be used in the subsequent request handlers to generate log events with the requestId automatically attached.

const baseLogger = initializeLogger();
app.use(requestIdAndLogger(baseLogger));

app.get("/testing", (req, res) => {
  const logger = res.locals.logger;
  const requestId = res.locals.requestId;

  logger.info(`The requestId is: ${requestId}`);

  // produces log event with { "requestId": "XXXXXXXXXXXX" } as a field
  logger.info("Inside the /testing request handler!");
});

Async request handler wrapper

The asyncHandlerWrapper function wraps an async request handler to automatically catch any rejected promises, log the error, and return an appropriate response. Without this function the default behavior is for unhandled promise rejections to crash the application, which is obviously unwanted.

This function requires that the requestIdAndLogger middleware function is installed.

import {
  asyncHandlerWrapper,
  requestIdAndLogger,
} from "@saasquatch/express-boilerplate";

app.use(requestIdAndLogger(baseLogger));

// BAD -- promise rejection in getSomethingFromTheDatabase will crash
// the application
app.get("/testing", async (req, res) => {
  const dbResults = await getSomethingFromTheDatabase();
  res.status(200).json(dbResults);
});

// OK -- promise rejection in getSomethingFromTheDatabase will be handled by
// the wrapper and return an appropriate 500 response
app.get(
  "/testing",
  asyncHandlerWrapper(async (req, res) => {
    const dbResults = await getSomethingFromTheDatabase();
    res.status(200).json(dbResults);
  }),
);

nanoid re-export

The library exports a nanoid function, which is just vanilla nanoid with a custom alphabet of characters that are a bit easier to read. Its default ID length is 32.

import { nanoid } from "@saasquatch/express-boilerplate";

console.log(nanoid());
console.log(nanoid(12));
1.1.0

1 year ago