Glossary
Node reuses ordinary JavaScript vocabulary in some places and adds runtime-specific terms in others — libuv, highWaterMark, backpressure have no browser equivalent. When a lesson uses a term you’re not sure of, look it up here.
Runtime and the event loop
Section titled “Runtime and the event loop”| Term | Meaning |
|---|---|
| Event loop | The single-threaded mechanism that lets Node handle many concurrent operations without blocking: it delegates slow work (I/O, timers, some crypto) to the OS or the threadpool, then runs callbacks as results arrive. See Event Loop & Async. |
| Phase | One stage of a single event loop iteration (timers, pending callbacks, poll, check, close callbacks, and more). Node runs a whole phase’s due callbacks before moving to the next one — not just one callback at a time. See Event Loop & Async. |
| libuv | The C library underneath Node that implements the event loop itself, the threadpool, and the OS-level async I/O primitives (epoll, kqueue, IOCP) that make non-blocking I/O possible. See Event Loop & Async. |
| Threadpool | A fixed-size pool of worker threads (default 4, tunable with UV_THREADPOOL_SIZE) that libuv uses for work with no native async OS API — async fs, crypto.pbkdf2/scrypt/randomBytes, dns.lookup, and zlib. Network sockets never use it. See The fs Module. |
| Macrotask / microtask | A macrotask is one task-queue entry (a setTimeout/setInterval callback, an I/O callback). A microtask is a Promise reaction or a queueMicrotask callback. The microtask queue (and process.nextTick) drains completely after every single callback, not once per loop iteration. See Microtasks & Macrotasks. |
process.nextTick | Schedules a callback to run before the microtask queue on the current drain — a separate, higher-priority queue than Promise microtasks. See Microtasks & Macrotasks. |
| Tick | Loosely, one pass through the process.nextTick queue. Precise usage always refers to that specific queue, not “one iteration of the event loop.” See Microtasks & Macrotasks. |
| Callback | A function passed to another function to be invoked later, once an operation finishes. Node’s original async pattern, conventionally error-first: (err, result) => .... See Callbacks. |
Promise / async / await | A Promise represents a value that will exist eventually (pending, fulfilled, or rejected). async/await is syntax for consuming promises as if they were synchronous, suspending the function at each await without blocking the event loop. See Promises and async/await. |
| Unhandled rejection | A Promise that rejects with nothing to catch it (no .catch(), no surrounding try around an await). Since Node 15 this crashes the process by default instead of only warning. See async/await. |
| Backpressure | The signal a slow consumer sends a fast producer to stop, so writes don’t pile up in memory faster than they can drain. In streams, writable.write() returning false is that signal. See Backpressure. |
highWaterMark | The buffer-size threshold a stream uses to decide when to apply backpressure — 64 KiB (65536 bytes) by default for byte streams, 16 objects in object mode. See Backpressure. |
| Chunk | One piece of data emitted by a Readable’s data event or passed to a Writable’s write() — a Buffer (or a string, or any value in object mode), never the whole payload at once. See Readable Streams. |
| Readable / Writable / Duplex / Transform | The four stream base classes: Readable produces data, Writable consumes it, Duplex does both independently (a TCP socket), and Transform is a Duplex that maps input to output (zlib.createGzip()). See Streams & I/O. |
pipeline | stream.pipeline() connects a chain of streams and, unlike .pipe(), forwards errors from any stream in the chain and cleans up every stream once one fails or finishes. See pipe() and pipeline(). |
| Object mode | A stream mode where chunks are arbitrary JavaScript values instead of Buffer/string data, which changes highWaterMark’s unit from bytes to object count. See Streams & I/O. |
EventEmitter | The base class behind Node’s event-driven APIs (http.Server, streams, process). .on() registers a listener; .emit() calls every listener for that event name synchronously, in registration order. See EventEmitter. |
| Listener leak | Attaching listeners to the same EventEmitter event without ever removing them, so the listener array grows unbounded over the process’s life. Node warns past 10 listeners on one event with MaxListenersExceededWarning. See EventEmitter. |
AbortController / AbortSignal | AbortController creates a cancellation token (.signal) that async APIs like fetch accept. Calling .abort() — or a AbortSignal.timeout(ms) firing on its own — rejects the pending operation instead of leaving it running forever. See fetch and Outbound Requests. |
Modules and packages
Section titled “Modules and packages”| Term | Meaning |
|---|---|
| CommonJS (CJS) | Node’s original module system: require() loads a module synchronously and returns a copy of its module.exports object as it existed at the moment of the call. See Modules: CommonJS vs ESM. |
| ESM | The ECMAScript Module system (import/export) standardized in JavaScript itself. Resolved statically, before any code runs, which is what makes tree-shaking possible. See Modules: CommonJS vs ESM. |
| Live binding | ESM’s import gives you a reference to the exporting module’s variable itself, not a snapshot copy — if the exporting module later reassigns that variable, every importer sees the new value. CJS require() never does this. See Modules: CommonJS vs ESM. |
| Module resolution | The deterministic algorithm Node uses to turn a specifier ('express', './util.js') into an actual file on disk — checking node_modules, file extensions, and the exports field along the way. See Module Resolution. |
exports / imports map | exports in package.json is the modern, authoritative entry-point map controlling what a package’s consumers can import/require, and from which subpaths — it takes precedence over main. imports is the equivalent map for a package’s own internal #-prefixed subpath imports. See Module Resolution. |
| Condition | A named key inside an exports/imports map ("import", "require", "types", "node", "default") that Node picks between based on how the module is being loaded. Order matters — "types" must come first for TypeScript tooling to see it. See Module Resolution. |
Package scope / "type" | The nearest package.json above a file sets that file’s default module system via "type": "module" treats .js files as ESM; anything else (or its absence) treats them as CommonJS. See package.json Deep Dive. |
| Lockfile | A file (package-lock.json) that pins the exact resolved version of every dependency, direct and transitive, so an install is reproducible across machines. See Dependencies. |
Semver caret / tilde / ^0.x | ^1.2.3 allows any 1.x.x upgrade (minor and patch); ~1.2.3 allows only patch upgrades within 1.2.x. For a 0.x version, npm treats the leading 0 as itself unstable, so ^0.4.2 allows only patch upgrades (0.4.x) — the same range ~0.4.2 would give. See package.json Deep Dive. |
| Peer / dev / optional dependency | peerDependencies declares a package the consumer must supply themselves (npm 7+ auto-installs it, warning on a version mismatch). devDependencies are tools needed only to build or test, never shipped to consumers. optionalDependencies install best-effort and don’t fail the whole install if they can’t build. See Dependencies. |
npm ci | Installs strictly from the lockfile, deleting any existing node_modules first and failing outright if package.json and the lockfile disagree. The reproducible-install command for CI and Docker builds, unlike npm install, which can update the lockfile. See Dependencies. |
| Workspace | An npm feature for managing multiple packages in one repository (a monorepo) from a single root package.json, with cross-package dependencies symlinked into each package’s node_modules instead of published and reinstalled. See Dependencies. |
node: prefix | Explicitly marks a specifier (node:fs, node:path) as a built-in module. Optional for long-standing built-ins, but mandatory for newer ones like node:test and node:sqlite, which have no unprefixed form. See Core APIs. |
| Term | Meaning |
|---|---|
| Request lifecycle | The sequence an incoming HTTP request goes through: headers parsed into an IncomingMessage, the body streamed in (if any), your handler runs and writes to a ServerResponse, then the connection closes or stays open for the next request. See The HTTP Server. |
IncomingMessage / ServerResponse | The two objects http.createServer’s callback receives. IncomingMessage (req) is a readable stream of the request (method, URL, headers, body). ServerResponse (res) is a writable stream used to send status, headers, and body back. See The HTTP Server. |
| Keep-alive | Reusing one TCP connection for multiple sequential requests instead of opening a new socket per request, controlled by the Connection: keep-alive header and, server-side, server.keepAliveTimeout. See The HTTP Server. |
| Chunked transfer | An HTTP/1.1 encoding (Transfer-Encoding: chunked) that streams a response body in pieces of unknown total size, used automatically whenever you write to res without first setting Content-Length. See The HTTP Server. |
Content-Length | A response header declaring the exact byte size of the body up front. Node sets it automatically only when res.end(data) is called in one shot with a size it can compute; otherwise the response falls back to chunked encoding. See The HTTP Server. |
| Headers sent | Once res.writeHead() or the first res.write()/res.end() call flushes the headers, they can’t be changed — setting a header or status code afterward throws ERR_HTTP_HEADERS_SENT. See The HTTP Server. |
| Body limit | An explicit cap on how many bytes of a request body a handler accepts before aborting the connection, protecting the server from a client streaming an unbounded payload into memory. See A Small JSON API. |
headersTimeout / requestTimeout / keepAliveTimeout | Three independent server timeouts with different defaults: headersTimeout (60 s) caps how long Node waits for request headers to finish arriving; requestTimeout (300 s) caps the whole request; keepAliveTimeout (5 s) caps how long an idle keep-alive connection stays open. See The HTTP Server. |
| Graceful shutdown | Stopping a server by first calling server.close() — which stops accepting new connections but lets in-flight requests finish — in response to SIGTERM, rather than exiting immediately and cutting off requests mid-response. See The HTTP Server. |
fetch / undici | Node’s built-in fetch (global since Node 18, stable since 21) is implemented by undici, Node’s own HTTP client. It only rejects on a network-level failure, never on a non-2xx HTTP status. See fetch and Outbound Requests. |
AbortSignal.timeout() | Creates an AbortSignal that fires on its own after the given number of milliseconds, letting you pass a request timeout straight into fetch({ signal }) without wiring up a setTimeout and an AbortController by hand. See fetch and Outbound Requests. |
| CORS | Cross-Origin Resource Sharing: response headers (Access-Control-Allow-Origin and friends) that tell a browser it’s allowed to let a script on one origin read a response from another. Irrelevant to server-to-server calls, which fetch/undici never block. See fetch and Outbound Requests. |
URL / URLPattern | The built-in URL class parses and constructs URLs (pathname, searchParams, …). URLPattern (global since Node 24) matches a URL’s path against a route pattern like /items/:id, with no routing library needed. See Routing. |
Testing and tooling
Section titled “Testing and tooling”| Term | Meaning |
|---|---|
| Test runner | node:test, Node’s built-in test runner — no install required, run with node --test. Discovers *.test.js files and reports results in TAP format. See node:test. |
describe / it | node:test’s grouping functions: describe names a block of related tests, it (an alias of test) defines one test case inside it. See node:test. |
| Mock | A stand-in implementation substituted for a real dependency so a test can control its behavior and assert how it was called — created in node:test with t.mock.fn(), t.mock.method(), or t.mock.module(). See node:test. |
| Fake timers | Replacing the real clock (setTimeout, Date) with a controllable one during a test, via t.mock.timers, so time-dependent code can be tested by advancing the fake clock instead of waiting in real time. See node:test. |
| Coverage | The percentage of lines or branches a test run actually executed, measured with node --test --experimental-test-coverage (still experimental on Node 24/26) — a necessary but not sufficient signal of test quality. See node:test. |
| Snapshot | A saved copy of a value’s serialized output that a later test run compares against, failing if the output changed unexpectedly. node:test supports this via t.assert.snapshot (stable since 23.4). See node:test. |
| Inspector | Node’s built-in debugging protocol, enabled with node --inspect, that lets Chrome DevTools or VS Code attach to a running process to set breakpoints and step through code. See Debugging Node.js. |
--watch | A Node CLI flag that restarts the process automatically whenever a required file changes — stable for running an app, still experimental when combined with --test. See The Node.js Ecosystem. |
--env-file | A Node CLI flag that loads a .env file’s key-value pairs into process.env before your code runs, with no third-party package needed (stable since 22.21/24.10). See The Node.js Ecosystem. |
| Strip-types | Node’s built-in ability to run .ts files directly by stripping type annotations at load time, with no transpilation of syntax that has no JS equivalent — node app.ts with no build step, for straightforward TypeScript. See The Node.js Ecosystem. |
perf_hooks | The built-in module (performance.now(), performance.mark()/.measure(), PerformanceObserver) for measuring elapsed time and collecting performance entries with sub-millisecond precision. See Performance Basics. |
| Event-loop lag | The delay between when a timer or callback should fire and when it actually does, caused by synchronous work blocking the loop. Measured with perf_hooks’s monitorEventLoopDelay(). See Performance Basics. |
| Worker thread | A worker_threads instance: a separate V8 isolate with its own event loop, running in parallel on another OS thread — used for CPU-bound work that would otherwise block the main thread. See Performance Basics. |
| Cluster | The built-in cluster module, which forks multiple Node processes that share one listening port so incoming connections are distributed across them — an alternative to (or paired with) a reverse proxy for scaling across CPU cores. See Performance Basics. |
Reading AI-generated code: smells to look for
Section titled “Reading AI-generated code: smells to look for”Generated Node.js runs cleanly in the happy path far more often than it’s actually correct under load, malformed input, or a slow dependency — these are the patterns most likely to look fine in a demo while quietly breaking in production.
| Pattern | Why it’s a smell | Covered in |
|---|---|---|
Forgotten await | Calling an async function without await inside another async function returns a Promise the caller never waits for — the rejection escapes unnoticed, and any later code that depends on the result runs against an unresolved value instead. | async/await |
.forEach(async fn) | Array.prototype.forEach ignores the return value of its callback entirely, so the promises an async callback returns are never awaited or collected — the loop looks synchronous but every iteration actually runs uncoordinated, with failures silently swallowed. | async/await |
Unbounded Promise.all | Mapping every item of a large array through an async call and awaiting them all as one Promise.all fires every request at once, with no concurrency cap — this can exhaust sockets, memory, or a downstream service’s rate limit. | Promises |
| Synchronous fs/CPU work in a request handler | Calling fs.readFileSync or running a heavy synchronous loop inside an HTTP handler blocks the single event loop thread, stalling every other in-flight request until it finishes. | The fs Module |
res.end() called twice / no error listener | Calling res.end() on a response that already ended throws ERR_STREAM_WRITE_AFTER_END; without an error listener on the request, response, or server, that throw crashes the whole process instead of just failing one request. | The HTTP Server |
| No request body size limit | Concatenating incoming req chunks into a buffer with no upper bound lets a client stream an unbounded body and exhaust server memory before the handler ever inspects it. | A Small JSON API |
fetch without checking .ok or a timeout | fetch only rejects on a network failure, never on a 4xx/5xx status — code that assumes a resolved promise means success silently processes error bodies as data. Without AbortSignal.timeout(...), a hung server leaves the request pending forever. | fetch and Outbound Requests |
| Listener leak | Attaching a new EventEmitter listener on every request or connection without ever removing it grows the listener array unboundedly. Past 10 listeners on one event, Node’s MaxListenersExceededWarning is a real memory-leak symptom, not just noise to silence. | EventEmitter |
process.env.PORT used as a number | process.env.X is always a string or undefined. Using it directly in arithmetic or a comparison like port > 1024 coerces the string, and an unset variable is undefined, not a fallback default, unless the code checks for it explicitly. | process.env and process.argv |
require and import mixed / __dirname in ESM | Mixing a require() call into a "type": "module" file throws a ReferenceError; __dirname/__filename don’t exist in ESM at all — import.meta.dirname (or fileURLToPath(import.meta.url)) is the replacement. | Modules: CommonJS vs ESM |
^0.x range / no lockfile | ^0.4.2 only allows patch bumps (not minor), because npm treats a 0.x major as itself unstable — code that assumes ^ always means “minor and patch” silently pins tighter than intended. Committing no lockfile at all means every install can resolve different transitive versions on different machines or in CI. | package.json Deep Dive |