Skip to content

Writing

The checklist I run before a Node.js service goes to production

Configuration, connection limits, timeouts, graceful shutdown, health checks and logging — the unglamorous items that decide whether a Node service survives its first bad week.

3 min readNode.js, DevOps, Production

Every Node.js service I have put into production has taught me the same lesson in a slightly different accent: the code that gets a feature working and the code that survives a bad Tuesday are not the same code. What follows is the list I actually walk through before a service takes real traffic. None of it is clever. All of it has cost me an evening at least once.

Configuration comes from the environment, always

If a hostname, a credential or a feature flag is written in a source file, the same artifact cannot be promoted from staging to production, and the two environments start to drift the moment anyone is in a hurry.

Read configuration once, at startup, and validate it there:

const config = {
  port: Number(process.env.PORT ?? 3000),
  databaseUrl: required('DATABASE_URL'),
  redisUrl: required('REDIS_URL'),
  logLevel: process.env.LOG_LEVEL ?? 'info',
};
 
function required(key: string): string {
  const value = process.env[key];
  if (!value) throw new Error(`Missing required environment variable: ${key}`);
  return value;
}

A service that refuses to start with a missing variable is far kinder than one that starts happily and fails on the first request that needs it.

Bound every pool, and know the number

A Postgres connection pool with no explicit maximum will cheerfully open more connections than the database will accept. Under normal load nothing happens. Under the load that matters, every instance opens its maximum simultaneously and the database starts refusing connections — including the ones your health check needs.

Work the arithmetic out before deploying: instances × pool size must sit comfortably below the database's connection limit, with headroom for migrations and for whoever is connecting with psql to find out what is going on.

Nothing waits forever

Anything crossing a network needs a timeout: HTTP calls to third parties, database queries, Redis commands. The default in most clients is no timeout at all, which means a hung dependency does not fail — it accumulates. Requests queue, the event loop stays busy, memory climbs, and the service dies of something that looks nothing like the original cause.

Set a timeout, decide what a timed-out call should return, and make that path as well-tested as the happy one.

Shut down on purpose

Container orchestrators send SIGTERM and then wait a short, finite time before sending SIGKILL. A service that ignores the first signal has its in-flight requests severed by the second one. Every rolling deploy then produces a small burst of errors that nobody can quite explain.

process.on('SIGTERM', async () => {
  server.close();            // stop accepting new connections
  await drainInFlight();     // let current requests finish
  await pool.end();          // release database connections
  process.exit(0);
});

Health checks that mean something

A health endpoint returning 200 because the process is running tells you the process is running. That is not the question being asked. The question is whether this instance can serve a request — which usually means it can reach its database and its cache.

Keep two endpoints. Liveness answers is this process wedged. Readiness answers should traffic be sent here right now. Conflating them causes a load balancer to keep sending work to an instance that cannot do any.

Logs a stranger can use

Structured logs with a request identifier threaded through them turn a two-hour investigation into a ten-minute one. Log the decision points, not every line of execution. Never log credentials, tokens or personal data — a log aggregator is a database with weaker access control and longer retention than the one you were careful about.

Test the rollback before you need it

The first time a rollback runs should not be during an incident. Deploy, roll back, deploy again, all while the service is unimportant. If a database migration makes rollback impossible, that is worth discovering on a quiet afternoon rather than at nine on a Friday evening.


None of these items is difficult. They are just easy to postpone, because each one only matters on the day it matters. The whole list takes perhaps a day to work through properly, and it is the cheapest day of engineering time in the entire lifespan of a service.

Get in touch

Working on something that needs to hold up in production?

I am open to conversations about engineering roles, contract work and interesting problems — particularly where application development, infrastructure and reliability meet. The fastest way to reach me is email.

rajeevmohank@gmail.com
Based in
Palakkad, Kerala, India
Currently
Software Engineer at Amigosia