Backend Node.js Course · Level 1
Node.js process/env and Error Handling Patterns
On this page
1. Error-first callback convention
- Legacy callbacks in Node (
fs.readFile, legacy DB drivers...) always place the error argument as the first parameter:(err, data) => {...}. - Core reason: placing
errat a fixed first position establishes a unified pattern that enables "fail fast":if (err) return callback(err), applicable across all asynchronous functions in the Node ecosystem without needing to remember different parameter positions per function. - JavaScript does not automatically throw errors or warn if you forget to check
err; the code continues running normally, which easily leads to hard-to-debug issues far away from where they originated. Use ESLint (rules such ashandle-callback-err) to prevent forgetting this check. - Drawback: when multiple asynchronous operations run sequentially, this pattern causes callback hell (deeply nested layers, difficult to read).
2. Promise and async/await
- Promise
.then().then().catch()solves callback hell but can still create "promise chain hell" if the chain grows too long. async/awaitcombined withtry/catchsolves both problems: code reads sequentially and naturally like synchronous code, and consolidates errors from multiple consecutiveawaitsteps into a singlecatchblock:
js
async function main() {
try {
const user = await getUser(id);
const orders = await getOrders(user.id);
const details = await getOrderDetails(orders[0].id);
} catch (err) {
console.error(err);
}
}
- If any
awaitline inside throws an error, execution jumps directly tocatch, equivalent to a.catch()at the end of a Promise chain but much more natural to read.
3. Unhandled Promise rejection
- If a Promise is rejected without a
.catch()or without being wrapped intry/catch, Node does not silently swallow the error; instead, it outputs anUnhandledPromiseRejectionWarning. - From Node 15 onwards (the current default): an unhandled rejection causes the process to crash entirely, treated as a severe error equivalent to an uncaught exception. Prior to Node 15, it only printed a warning and continued running.
- Reason Node switched to crashing entirely: if it only warns and continues running, each unhandled rejected Promise retains its reference in memory (in case a
.catch()is attached later). Repeating this continuously leads to a memory leak; RAM is gradually occupied without being freed, causing the server to run for a few days before crashing unexpectedly due to running out of RAM, making it very difficult to trace the root cause. - Crashing immediately (fail fast) exposes bugs early during dev/test, rather than silently causing prolonged memory leaks in production.
- Prevention: always include
.catch()ortry/catchfor every Promise; you can also listen toprocess.on('unhandledRejection', ...)at the top level to log before crashing.
4. process.env and environment variables
process.envis an object containing all environment variables. It is used to store sensitive values (DB connection strings, API keys, secrets) instead of hardcoding them, preventing exposure when pushing code to git, and allowing each environment (dev/staging/production) to use different values without modifying code.- Node does not automatically read
.envfiles and load them intoprocess.env. By default, it only reads environment variables that already exist at the OS level (set via the terminal prior to execution). - The
dotenvlibrary reads the.envfile, parses its contents (KEY=value), and manually assigns each pair toprocess.env, typically invoked viarequire('dotenv').config(). dotenv.config()must be called on the very first line of the entry point file, before any other imports. If called late (e.g. at the bottom of the file), other modules imported earlier that readprocess.envimmediately upon load (like a DB connection file) will receiveundefined, causing connection failures even if.envhas the correct data.- Do not commit the actual
.envfile to git (add it to.gitignore). Use.env.examplelisting the required variable names (without actual values) to commit to git, helping other team members know what needs to be configured.
