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.
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
0is success. Useprocess.argv,process.env,process.exitCode. - Prefix built-ins:
node:fs,node:path,node:http,node:test.
| File | Treated as |
|---|---|
| .mjs | Always ESM |
| .cjs | Always CommonJS |
| .js | ESM 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 →
- Sync code runs to its end first.
process.nextTickqueue, then promise microtasks (ESM: promises first).- Timers, pending, idle, poll (I/O), check (
setImmediate), close. - 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
nextTickor promises starve I/O and timers. Chunk withsetImmediate. awaitpauses the function, not the thread. Callback errors do not travel up.
Files and pathsLesson 2 →
- Use
node:fs/promisesin servers.readFileSyncfroze 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 callaccessfirst (race). - Flag
"wx"creates only if the file is new. - Atomic write: write a temp file, then
rename.fs.watchfires 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 symlinksAnchor paths on import.meta.dirname, never the cwd. Use path.join, not string glue.
- Buffers are raw bytes outside the heap.
subarrayshares memory;allocUnsafemay leak old data. - Decoding chunk by chunk can split a multi-byte character.
Streams and backpressureLesson 3 →
| Kind | Examples |
|---|---|
| Readable | createReadStream, incoming request, stdin |
| Writable | createWriteStream, response, stdout |
| Duplex | TCP socket |
| Transform | createGzip(), 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()returningfalsemeans wait fordrain. Ignoring it queued 625 MB.pipe()left the source open when the target failed. Usepipeline.- Chunks are not lines. Use
readline. Skip streams for tiny data.
Raw HTTP serverLesson 4 →
- Handler is
(req, res):reqis readable,resis writable. Headers are lower case; query values are strings. - One
try/catch: known errors carry a status (HttpError), unknown ones log and return500. - Limit body size (
413), check content type (415), bad JSON (400).404no path,405wrong method. - Use
Buffer.byteLengthforContent-Length.listen(0)picks a free port (tests). - 50 requests with a 100 ms wait finished in 132 ms. One sync route freezes all.
| Setting | Limits | Default |
|---|---|---|
| keepAliveTimeout | Idle kept connection | 5000 ms |
| headersTimeout | Receiving headers | 60000 ms max |
| requestTimeout | Whole request | 300000 ms |
| timeout | Socket inactivity | 0 (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 argsOrder is everything: logger, parsers, routes, 404, then the error handler last.
| Topic | Express 5 | Fastify 5 |
|---|---|---|
| Validation | Bring a library | JSON Schema built in |
| Async errors | Caught (new in 5) | Caught |
| Logging | Add a library | pino built in |
| Structure | Ordered middleware | Hooks and plugins |
| Default error | HTML page | JSON object |
- Middleware is
(req, res, next): answer, callnext(), 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"into7. A response schema filters leaks.
Errors, logs, health, shutdownLesson 6 →
| Kind | Example | Plan |
|---|---|---|
| Operational | ENOENT, ECONNREFUSED, bad input | Handle, retry, 4xx or 5xx |
| Programmer | TypeError, bad state | Log, restart |
try/catchmisses callbacks and floating promises. An unhandled rejection crashes the process (Node 15+): usually right.uncaughtExceptionandunhandledRejectionlisteners: 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
503during shutdown. - Exit codes:
0success,1failure,128+Nsignal (SIGTERMis 143).
- On
SIGTERMset a flag so readiness fails. server.close(): refuse new connections, finish in-flight work.- Close the database and other resources.
- Exit with
process.exitCode, with a safety timer. Test withcurlandkill.
Concurrency toolsLesson 7 →
| Call | Result | Use when |
|---|---|---|
| Promise.all | All values; rejects on first error | Need every result |
| Promise.allSettled | Each status, never rejects | Partial success fine |
| Promise.race | First to settle | Timeouts |
| Promise.any | First success | Mirrors |
- Independent awaits go in
Promise.all: 1507 ms in sequence against 301 ms. Promise.alldoes not cancel the rest. UseAbortSignal.timeout(ms)on every network call.- Unlimited concurrency failed 25 of 30 requests. A
mapLimitkept a peak of 5. fib(40)made/healthtake over 1 s. In a worker it took 3 ms.execwith user input is injection: useexecFileorspawnwith an argument array.
| Problem | Tool |
|---|---|
| Heavy JS in a request | worker_threads pool |
| Idle cores, no orchestrator | cluster or PM2 |
| Orchestrator exists | One process per container |
| Long job | Queue 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.
monitorEventLoopDelayshowed a 300 ms block as a 309 ms maximum.- Leak:
heapUsedkeeps 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
PORTis missing. Real environment beats.env. - Docker: pin the base image, cache dependencies, run as non-root
nodeuser. - Start with
CMD ["node", "server.js"]so Node getsSIGTERM.npm startmay 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,prototypekeys; useObject.create(null)orMap. - 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 →
| Wrong | Right |
|---|---|
| Single thread means one thing at a time | Waits on many, runs JS one piece at a time |
async makes heavy work non-blocking | It stays on your thread |
Catch in uncaughtException, carry on | Log, shut down, restart |
Promise.all cancels the others | It does not |
pipe handles errors | Use pipeline |
Always use cluster | One process per container |
Interview one-linersLesson 9 →
- Default thread pool
- 4
- nextTick or promise first?
- CJS nextTick; ESM promise
- exit or exitCode?
exitCode;exitcan 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.