Node backend setup
A Node backend that starts as a quick index.js and grows accretes the
same missing foundations every time: no config discipline, no graceful
shutdown, console.log everywhere. Putting the foundations in at the start
costs an hour and saves a rewrite.
Method
- Start with ESM and TypeScript.
"type": "module", a tsconfig targeting your Node version (see tsconfig-mastery), and a runner (tsx/ts-nodefor dev, compiled output or a bundler liketsupfor prod). Modern Node runs TypeScript-adjacent setups cleanly; pick one and stay consistent to avoid the module-resolution pain (see js-modules). - Load and validate config once, typed. Read environment variables
at startup into a validated, typed config object (a schema validator:
zod/envalid), failing fast with a clear message if a required var is
missing or malformed (see environment-config, request-validation). No
process.env.FOOscattered through the code; one config module every file imports. - Implement graceful shutdown from day one. Trap
SIGTERM/SIGINT: stop accepting new connections, finish in-flight requests, close the DB pool and other resources, then exit; with a timeout that forces exit if drain hangs (see graceful-shutdown). Every deploy and scale-in sends SIGTERM; without this, each one drops requests. - Use structured logging, not console.log. A logger (pino/winston)
emitting JSON with levels, a request/correlation id, and no secrets
(see structured-logging, log-levels).
console.loggives you unsearchable, unleveled noise the moment the service sees real traffic. - Add health and readiness endpoints. A liveness check (process is up) and a readiness check (dependencies reachable) so orchestrators route and restart correctly (see health-checks); the difference matters the first time a dependency blips.
- Handle the process-level failures. Register
unhandledRejectionanduncaughtExceptionhandlers that log and exit cleanly (a crashed process is better than a zombie in a bad state; let the orchestrator restart it), and never leave them unset (see js-error-handling).
Boundaries
- This is the service skeleton; the framework choice (Express, Fastify, Hono, Nest) and the API design sit on top (see rest-endpoint-design, api-design). Pick the framework for the project, but the foundations above are framework-independent.
- Clustering and multi-core scaling (Node uses one core per process) is a
deployment decision (a process manager or the orchestrator running N
replicas), usually better than the in-process
clustermodule. - Long CPU-bound work blocks the event loop and belongs in worker threads or a separate service, not the request path (see js-event-loop).