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/livemeans "the process is up";/health/readymeans "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
enginesand.nvmrcso 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.queryis a read-only getter.res.status()rejects out-of-range codes;app.del()and other legacy aliases are gone.
Example
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.
// 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.
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.
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