Senior Tips & Tricks

A grab-bag of production wisdom: health checks, async local storage for context, disable x-powered-by, and know your Express 5 breaking changes.

The small stuff that separates an app that merely runs from one that operates well at 2am.

Operational hygiene

  • Separate liveness from readiness. /health/live means "the process is up"; /health/ready means "dependencies are reachable, send me traffic." Orchestrators use them differently.
  • Disable the fingerprint. app.disable('x-powered-by') stops advertising "Express" to attackers scanning for framework-specific exploits.
  • Pin your Node version in engines and .nvmrc so dev, CI and prod agree.

Request context without prop-drilling

AsyncLocalStorage (built into node:async_hooks) lets you stash the request id or current user in a store that any function deep in the call stack can read — without threading req through every signature. It is how request-scoped logging works cleanly.

Know the Express 5 breaking changes

  • Async errors are auto-caught (covered earlier).
  • Path syntax changed: named wildcards (*splat), braces for optional (/a{/b}).
  • req.query is a read-only getter.
  • res.status() rejects out-of-range codes; app.del() and other legacy aliases are gone.

Example

Example · javascript
import express from 'express';
import { AsyncLocalStorage } from 'node:async_hooks';
import { randomUUID } from 'node:crypto';

export const requestContext = new AsyncLocalStorage();

const app = express();
app.disable('x-powered-by');           // stop advertising the framework
app.set('trust proxy', 1);

// Bind a per-request store; anything downstream can read it, no prop-drilling.
app.use((req, res, next) => {
  const store = { requestId: req.get('x-request-id') ?? randomUUID() };
  requestContext.run(store, next);
});

// A deep helper reads context without ever seeing `req`.
function log(msg) {
  const { requestId } = requestContext.getStore() ?? {};
  console.log(JSON.stringify({ requestId, msg }));
}

app.get('/work', (req, res) => {
  doDeepThing();                        // logs WITH the request id, magically
  res.json({ ok: true });
});

function doDeepThing() {
  log('did the deep thing');            // no req passed in, still correlated
}

// Liveness vs readiness — orchestrators treat them differently.
app.get('/health/live', (req, res) => res.json({ live: true }));
app.get('/health/ready', async (req, res) => {
  const ok = await checkDependencies();
  res.status(ok ? 200 : 503).json({ ready: ok });
});

When to use it

  • A team enforces a consistent error response shape by linting against any route handler that calls res.json() directly with an error key, requiring use of the shared error middleware instead.
  • A developer profiles the app with clinic.js flame charts to identify which middleware adds the most latency before optimising.
  • An ops engineer monitors the cluster worker memory with pm2 monit and sets max_memory_restart to auto-recycle workers that develop memory leaks.

More examples

Prevent information leakage in prod

Exposes the full error stack only in development, sending a generic 500 message to production clients to prevent stack trace leakage.

Example · js
// Global error handler — last middleware
app.use((err, req, res, next) => {
  const isDev = process.env.NODE_ENV !== 'production';
  console.error(isDev ? err : err.message);
  res.status(err.status || 500).json({
    error:   err.status < 500 ? err.message : 'Internal Server Error',
    ...(isDev && { stack: err.stack }),
  });
});

Health check endpoint

A health check route pings the database and returns 200/ok or 503/error so load balancers and monitoring tools can assess readiness.

Example · js
app.get('/health', async (req, res) => {
  try {
    await db.query('SELECT 1'); // confirm DB is reachable
    res.json({ status: 'ok', uptime: process.uptime() });
  } catch (err) {
    res.status(503).json({ status: 'error', detail: err.message });
  }
});

Avoid blocking the event loop

Offloads a CPU-intensive task to a worker thread so the main Express event loop is never blocked during computation.

Example · js
const { Worker } = require('worker_threads');

app.post('/process', (req, res) => {
  // Offload CPU-intensive work to a worker thread
  const worker = new Worker('./workers/compute.js', { workerData: req.body });
  worker.on('message', result => res.json({ result }));
  worker.on('error',   err    => res.status(500).json({ error: err.message }));
});

Discussion

  • Be the first to comment on this lesson.