Cheat sheet · Node.js From Zero: Event Loop, Streams and Servers

Node.js From Zero cheat sheet

Node.js on one page: the runtime and event loop, files, streams, servers, Express and Fastify, errors and shutdown, concurrency, tests, memory and config. Waiting is cheap, thinking is expensive.

Read the full series →

blogs · jp-blogs.vercel.app/cheatsheets/series/nodejs

What Node isLesson 0 →

  • V8 runs your JavaScript, libuv does the waiting, built-in modules give files, network and more.
  • Your JS runs on one thread. A pool of 4 threads (UV_THREADPOOL_SIZE) serves fs, DNS lookup, some crypto and zlib. Network I/O does not use it.
  • Exit code 0 is success. Use process.argv, process.env, process.exitCode.
  • Prefix built-ins: node:fs, node:path, node:http, node:test.
FileTreated as
.mjsAlways ESM
.cjsAlways CommonJS
.jsESM only if "type": "module"
node --watch app.js
Restart when a used file changes
node --env-file=.env app.js
Load variables; the real env wins
npm ci
Exact install from the lock file (CI)

Event loop orderLesson 1 →

  1. Sync code runs to its end first.
  2. process.nextTick queue, then promise microtasks (ESM: promises first).
  3. Timers, pending, idle, poll (I/O), check (setImmediate), close.
  4. After every callback the microtask queues empty again.
// CommonJS output order:
// sync, nextTick, then.. queueMicrotask,
// then setImmediate / setTimeout 0
setTimeout(f, 0);
setImmediate(g);
Promise.resolve().then(h);
process.nextTick(i);

In an I/O callback setImmediate always beats setTimeout 0. In the main script it varies.

  • A timer delay is a minimum: a 500 ms timer ran at 1252 ms after a 1 s block.
  • Recursive nextTick or promises starve I/O and timers. Chunk with setImmediate.
  • await pauses the function, not the thread. Callback errors do not travel up.

Files and pathsLesson 2 →

  • Use node:fs/promises in servers. readFileSync froze a 10 ms heartbeat for 1325 ms.
  • Say the encoding: readFile(f, "utf8"). Without it you get a Buffer.
  • Handle errors by e.code: ENOENT, EEXIST, EACCES. Do not call access first (race).
  • Flag "wx" creates only if the file is new.
  • Atomic write: write a temp file, then rename. fs.watch fires duplicate events.
const root = path.resolve(base);
const full = path.resolve(root, input);
if (!full.startsWith(root + path.sep))
  throw new Error("traversal");
// then fs.realpath for symlinks

Anchor paths on import.meta.dirname, never the cwd. Use path.join, not string glue.

  • Buffers are raw bytes outside the heap. subarray shares memory; allocUnsafe may leak old data.
  • Decoding chunk by chunk can split a multi-byte character.

Streams and backpressureLesson 3 →

KindExamples
ReadablecreateReadStream, incoming request, stdin
WritablecreateWriteStream, response, stdout
DuplexTCP socket
TransformcreateGzip(), createHash()
import { pipeline } from
  "node:stream/promises";
await pipeline(
  createReadStream("in.csv"),
  createGzip(),
  createWriteStream("in.csv.gz"),
);

Handles backpressure, forwards errors, destroys all streams on failure.

  • Memory follows chunk size: 82 MB against about 850 MB for a 302 MB file.
  • write() returning false means wait for drain. Ignoring it queued 625 MB.
  • pipe() left the source open when the target failed. Use pipeline.
  • Chunks are not lines. Use readline. Skip streams for tiny data.

Raw HTTP serverLesson 4 →

  • Handler is (req, res): req is readable, res is writable. Headers are lower case; query values are strings.
  • One try/catch: known errors carry a status (HttpError), unknown ones log and return 500.
  • Limit body size (413), check content type (415), bad JSON (400). 404 no path, 405 wrong method.
  • Use Buffer.byteLength for Content-Length. listen(0) picks a free port (tests).
  • 50 requests with a 100 ms wait finished in 132 ms. One sync route freezes all.
SettingLimitsDefault
keepAliveTimeoutIdle kept connection5000 ms
headersTimeoutReceiving headers60000 ms max
requestTimeoutWhole request300000 ms
timeoutSocket inactivity0 (none)

Express 5 and Fastify 5Lesson 5 →

app.use(express.json());   // parsers
app.use(routes);
app.use((req, res) =>      // 404
  res.status(404).json({}));
app.use((err, req, res, next) =>
  res.status(500).json({}));  // 4 args

Order is everything: logger, parsers, routes, 404, then the error handler last.

TopicExpress 5Fastify 5
ValidationBring a libraryJSON Schema built in
Async errorsCaught (new in 5)Caught
LoggingAdd a librarypino built in
StructureOrdered middlewareHooks and plugins
Default errorHTML pageJSON object
  • Middleware is (req, res, next): answer, call next(), or pass an error.
  • Express 5 paths: *path, not *. The default error page can show file paths.
  • Fastify hooks: onRequest, preParsing, preValidation, preHandler, preSerialization, onSend, onResponse.
  • Schema coercion turns "7" into 7. A response schema filters leaks.

Errors, logs, health, shutdownLesson 6 →

KindExamplePlan
OperationalENOENT, ECONNREFUSED, bad inputHandle, retry, 4xx or 5xx
ProgrammerTypeError, bad stateLog, restart
  • try/catch misses callbacks and floating promises. An unhandled rejection crashes the process (Node 15+): usually right.
  • uncaughtException and unhandledRejection listeners: log and exit only. An emitter with no 'error' listener throws.
  • pino: JSON to stdout, levels, child logger with request id, redact secrets. Pretty-print only in dev.
  • Liveness stays light; readiness says "can I take traffic" and turns 503 during shutdown.
  • Exit codes: 0 success, 1 failure, 128+N signal (SIGTERM is 143).
  1. On SIGTERM set a flag so readiness fails.
  2. server.close(): refuse new connections, finish in-flight work.
  3. Close the database and other resources.
  4. Exit with process.exitCode, with a safety timer. Test with curl and kill.

Concurrency toolsLesson 7 →

CallResultUse when
Promise.allAll values; rejects on first errorNeed every result
Promise.allSettledEach status, never rejectsPartial success fine
Promise.raceFirst to settleTimeouts
Promise.anyFirst successMirrors
  • Independent awaits go in Promise.all: 1507 ms in sequence against 301 ms.
  • Promise.all does not cancel the rest. Use AbortSignal.timeout(ms) on every network call.
  • Unlimited concurrency failed 25 of 30 requests. A mapLimit kept a peak of 5.
  • fib(40) made /health take over 1 s. In a worker it took 3 ms.
  • exec with user input is injection: use execFile or spawn with an argument array.
ProblemTool
Heavy JS in a requestworker_threads pool
Idle cores, no orchestratorcluster or PM2
Orchestrator existsOne process per container
Long jobQueue plus a worker service

Tests and profilingLesson 8 →

node --test
Run built-in tests (*.test.mjs)
--test-name-pattern=404
Run matching tests only
--cpu-prof
Write a CPU profile; read self time
--heap-prof
Allocation sampling
--inspect
DevTools port; never expose publicly
--max-old-space-size=64
Heap limit; exceeding it exits 134
  • Use node:assert/strict, inject dependencies to fake them, start apps on port 0, test error paths.
  • Do not guess about speed: 88% of one run sat in one function.
  • monitorEventLoopDelay showed a 300 ms block as a 309 ms maximum.
  • Leak: heapUsed keeps rising after GC (441 MB unbounded against 4.7 MB bounded cache). Compare heap snapshots and read retainers.

Config and DockerLesson 8 →

  • Read config from the environment once at start-up, validate, convert types (all values are strings), freeze.
  • Fail fast if PORT is missing. Real environment beats .env.
  • Docker: pin the base image, cache dependencies, run as non-root node user.
  • Start with CMD ["node", "server.js"] so Node gets SIGTERM. npm start may not pass it on.

Tip: Before shipping: logs with request id, /health and /ready, tested shutdown, metrics for loop delay and heapUsed, npm ci, a rollback.

Security habitsLesson 9 →

  • Prototype pollution: reject __proto__, constructor, prototype keys; use Object.create(null) or Map.
  • ReDoS: avoid nested quantifiers on user input.
  • Compare secrets with timingSafeEqual.
  • Packages run with your full power. Commit the lock file, run npm audit.

Watch out: exec("echo " + name) with x; echo INJECTED ran both commands. execFile printed it as plain text.

Common wrong answersLesson 9 →

WrongRight
Single thread means one thing at a timeWaits on many, runs JS one piece at a time
async makes heavy work non-blockingIt stays on your thread
Catch in uncaughtException, carry onLog, shut down, restart
Promise.all cancels the othersIt does not
pipe handles errorsUse pipeline
Always use clusterOne process per container

Interview one-linersLesson 9 →

Default thread pool
4
nextTick or promise first?
CJS nextTick; ESM promise
exit or exitCode?
exitCode; exit can lose output
Stop a hanging fetch
AbortSignal.timeout(ms)
`server.close()`
No new connections, waits for open ones
`Object.create(null)`
No prototype, no __proto__ tricks
Unsure about speed?
Say how you would measure it
Where do errors go?
Rejections; log and exit if a bug

A cheat sheet is a reminder, not a teacher. Each card links to the lesson that explains it step by step.