Node.js Backend Fundamentals with Express · Data, Auth, Tests and Production

Configuration, Logging and Graceful Shutdown

Read configuration from the environment, log structured JSON, expose health checks, and shut down without dropping requests.

Create a free account to save this page, take notes and track your progress. Sign in

What you'll build

The production shell around an Express app: validated configuration from environment variables, JSON logs with pino, /healthz and /readyz endpoints, and a SIGTERM handler that stops accepting connections and finishes in-flight requests.

The idea

  • Configuration comes from the environment and is validated at startup: fail fast with a clear message if something is missing.
  • Structured logs (JSON) with a level, message and fields are searchable in log systems.
  • Health endpoints: liveness (the process works) and readiness (dependencies are reachable).
  • Graceful shutdown: on SIGTERM (sent by Docker and Kubernetes), mark not ready, stop accepting connections with server.close(), wait for in-flight requests, then exit.

The code

Config, JSON logs, health checks and SIGTERM
import express from "express";
import pino from "pino";
import { z } from "zod";

const config = z.object({
  PORT: z.coerce.number().int().default(0),
  LOG_LEVEL: z.enum(["debug", "info", "warn"]).default("info"),
  DATABASE_URL: z.url(),
}).parse({ ...process.env, DATABASE_URL: process.env.DATABASE_URL ?? "postgres://db.internal/shop" });

const lines = [];
const logger = pino({ level: config.LOG_LEVEL, base: undefined, timestamp: false }, { write: (l) => lines.push(l.trim()) });
let ready = true;
const app = express();
app.get("/healthz", (req, res) => res.json({ ok: true }));
app.get("/readyz", (req, res) => res.status(ready ? 200 : 503).json({ ready }));
app.get("/slow", async (req, res) => { await new Promise((r) => setTimeout(r, 300)); res.json({ done: true }); });

const server = app.listen(config.PORT, () => logger.info({ port: "free port" }, "listening"));
process.on("SIGTERM", () => {
  ready = false;
  logger.info("SIGTERM: draining");
  server.close(() => { logger.info("all connections closed, exiting"); console.log(lines.join("\n")); });
});

const base = `http://127.0.0.1:${server.address().port}`;
const inFlight = fetch(`${base}/slow`).then(async (r) => logger.info({ status: r.status }, "in-flight request finished"));
await new Promise((r) => setTimeout(r, 50));
process.kill(process.pid, "SIGTERM");
await inFlight;

Output

{"level":30,"port":"free port","msg":"listening"}
{"level":30,"msg":"SIGTERM: draining"}
{"level":30,"status":200,"msg":"in-flight request finished"}
{"level":30,"msg":"all connections closed, exiting"}

Run this on your computer — see “Get Started” for setup.

Common mistakes

  • Reading process.env all over the code, with no validation until something fails at 3 a.m.
  • console.log strings that can't be filtered or aggregated.
  • process.exit(0) on SIGTERM, dropping in-flight requests.
  • A readiness check that depends on the process only, so traffic arrives before the database is reachable.

Key points

  • Validate configuration at startup.
  • Log structured JSON with levels.
  • Separate liveness and readiness, and drain on SIGTERM before exiting.

Further reading: Express production best practices · pino · Node.js process signals

Practice questions

What should an Express app do when it receives SIGTERM?

Answer

Mark itself not ready, stop accepting new connections (server.close), let in-flight requests finish, then exit before the platform's grace period ends.

Why validate configuration at startup?

Answer

Missing or malformed settings fail immediately with a clear message, instead of causing confusing errors later when the code path is first used.

What's the difference between liveness and readiness endpoints?

Answer

Liveness says the process works (restart if not); readiness says it can serve traffic now, including dependencies (remove from load balancing if not).

Open in the app to mark lessons complete, take notes and track your progress.

Was this page helpful?