Node.js cheat sheet
A scannable Node.js reference: 25 short snippets across 12 topics, each linking back to the lesson it came from.
At a glance
| Topic | What it covers | |
|---|---|---|
| Node.js: getting started | Node.js runs JavaScript on the server using V8, the same engine as Chrome. Its defining trait is a single-threaded | lesson |
| Files and paths in Node | Reading a multi-gigabyte file into memory with readFile will exhaust the heap. Streams process it in chunks with | lesson |
| Building an HTTP server | A server with no framework, routing by hand, reading the body, and when to reach for Express-style libraries | lesson |
| Modules, ESM and CommonJS | Node supports two module systems side by side. CommonJS (require) is the original: synchronous, resolved at run time | lesson |
| Events, streams and buffers | Much of Node's API is an EventEmitter: objects that announce facts and let any number of listeners react. Emitting is | lesson |
| Async patterns, timers and the event loop | The event loop is a sequence of phases. Each phase runs its callbacks, then the microtask queue drains completely | lesson |
| Configuration, environment and CLI arguments | Read the environment once, at startup, and turn it into a validated object. Scattering process.env lookups through the | lesson |
| Errors, logging and debugging | Custom error classes with cause chaining, exit codes that mean something, structured logs, and the built-in inspector | lesson |
| Testing with the built-in test runner | Node ships a test runner. No framework, no configuration file: a file named *.test.js with node:test imports is | lesson |
| Databases, HTTP clients and external services | fetch with timeouts, retries and idempotency, connection pooling, migrations and transaction boundaries | lesson |
| Worker threads, child processes and clustering | Moving CPU-bound work off the event loop, running other programs safely, scaling across cores, and shutting every | lesson |
| Security, performance and deployment | Validate input at the boundary, limit and time every call, profile before optimising, and ship a small container that | lesson |
Quick snippets
Node.js: getting started
What Node.js is
node --version
node app.js
node # REPL
npm init -y # create package.json
Modules
// CommonJS (traditional)
const fs = require("fs");
module.exports = { helper };
// ES modules (modern; "type": "module" in package.json or .mjs)
import { readFile } from "node:fs/promises";
export function helper() {}
Configuration
const port = Number(process.env.PORT ?? 3000);
if (process.env.NODE_ENV !== "production") {
console.warn("running in development mode");
}
process.exitCode = 1; // prefer this over process.exit()Full lesson: Node.js: getting started →
Files and paths in Node
Reading and writing
import { readFile, writeFile, mkdir } from "node:fs/promises";
import path from "node:path";
const file = path.join(process.cwd(), "data", "config.json");
const text = await readFile(file, "utf8");
const config = JSON.parse(text);
await mkdir(path.dirname(file), { recursive: true });
await writeFile(file, JSON.stringify(config, null, 2), "utf8");
Paths done right
import path from "node:path";
import { fileURLToPath } from "node:url";
const here = path.dirname(fileURLToPath(import.meta.url));
const target = path.join(here, "..", "public", "index.html");
path.extname(target); // '.html'
path.basename(target); // 'index.html'
path.resolve("a", "b"); // absolute
path.normalize("a/../b"); // 'b'
Streams for large data
import { createReadStream, createWriteStream } from "node:fs";
import { pipeline } from "node:stream/promises";
import { createGzip } from "node:zlib";
await pipeline(
createReadStream("access.log"),
createGzip(),
createWriteStream("access.log.gz")
);Full lesson: Files and paths in Node →
Building an HTTP server
Framework or no framework
// the same idea in Express
import express from "express";
const app = express();
app.use(express.json({ limit: "100kb" }));
app.get("/health", (req, res) => res.json({ ok: true }));
app.post("/echo", (req, res) => res.json(req.body));
app.listen(3000);Full lesson: Building an HTTP server →
Modules, ESM and CommonJS
Two module systems, one runtime
// greeting.cjs — CommonJS
const os = require("node:os");
function greet(who) {
return "hello " + who;
}
module.exports = { greet, platform: os.platform() };
Two module systems, one runtime
// greeting.js — ES module ("type": "module")
import os from "node:os";
export function greet(who) {
return "hello " + who;
}
export const platform = os.platform();
Interop and dynamic import
// ESM can import CommonJS: the default export is module.exports
import legacy from "./greeting.cjs";
const name = legacy.greet("Ada");
// CommonJS cannot require() an ESM graph that awaits at the top level,
// so reach for the asynchronous form instead
async function load() {
const { greet } = await import("./greeting.js");
return greet("Ada");
}Full lesson: Modules, ESM and CommonJS →
Events, streams and buffers
EventEmitter
import { EventEmitter } from "node:events";
const bus = new EventEmitter();
bus.on("order.paid", (order) => console.log("ship", order.id));
bus.once("ready", () => console.log("first time only"));
bus.on("error", (err) => console.error("bus failed", err));
bus.emit("order.paid", { id: 1042 }); // listeners run right here
bus.listenerCount("order.paid"); // 1
bus.removeListener("order.paid", handler);
bus.removeAllListeners();
Buffers, encoding and backpressure
const buf = Buffer.from("hello", "utf8");
buf.length; // 5 — bytes, not characters
buf.toString("base64"); // "aGVsbG8="
Buffer.byteLength("héllo"); // 6 — the accent is two bytes
Buffer.concat([buf, Buffer.from("!")]); // one allocation, no string round-trip
// honour backpressure: write() returns false when the buffer is full
import { once } from "node:events";
if (!out.write(chunk)) {
await once(out, "drain");
}Full lesson: Events, streams and buffers →
Async patterns, timers and the event loop
What the loop actually runs
console.log("1 sync");
setTimeout(() => console.log("5 timeout"), 0);
setImmediate(() => console.log("6 immediate"));
queueMicrotask(() => console.log("3 microtask"));
process.nextTick(() => console.log("2 nextTick"));
Promise.resolve().then(() => console.log("4 promise"));
// sync, then nextTick, then promise/microtask,
// then timeout and immediate — which of the last two first is not guaranteed
// at the top level, but inside an I/O callback setImmediate always wins.
Timers and async discipline
import { setTimeout as sleep } from "node:timers/promises";
const id = setTimeout(() => console.log("late"), 5000);
clearTimeout(id);
// unref() lets the process exit even though the timer is pending
setTimeout(() => console.log("breathe"), 100).unref();
const started = performance.now();
await sleep(250); // awaitable, no callback pyramid
console.log(performance.now() - started);
Cancellation and failures
const controller = new AbortController();
const { signal } = controller;
await fetch(url, { signal });
await sleep(1000, { signal });
controller.abort(new Error("deadline exceeded")); // the reason is preserved
process.on("unhandledRejection", (reason) => {
console.error("unhandled rejection", reason);
process.exitCode = 1;
});Full lesson: Async patterns, timers and the event loop →
Configuration, environment and CLI arguments
process.env and env files
# the shell way
export DATABASE_URL=postgres://localhost/app
PORT=8080 NODE_ENV=production node server.js
# Node 20.6+ reads a file itself, no dotenv import needed
node --env-file=.env server.js
node --env-file-if-exists=.env.local server.js
process.env and env files
const required = ["DATABASE_URL", "JWT_SECRET"];
for (const key of required) {
if (!process.env[key]) {
throw new Error("missing required environment variable: " + key);
}
}
const port = Number(process.env.PORT ?? 3000);
if (!Number.isInteger(port) || port < 1 || port > 65535) {
throw new Error("PORT must be a valid port number");
}Full lesson: Configuration, environment and CLI arguments →
Errors, logging and debugging
Exit codes and structured logging
const log = (level, message, fields = {}) =>
process.stdout.write(
JSON.stringify({ time: new Date().toISOString(), level, message, ...fields }) + "\n"
);
log("info", "order created", { orderId: 1042, userId: 7 });
process.on("uncaughtException", (err) => {
log("fatal", "uncaught exception", { message: err.message, stack: err.stack });
server.close(() => process.exit(1));
});
Debugging a running process
node --inspect-brk src/server.js # pause on the first line, open chrome://inspect
node --inspect=0.0.0.0:9229 app.js # only inside a trusted container
node --watch src/server.js # restart on file change, development only
node --trace-warnings app.js
node --stack-trace-limit=50 app.js
node --cpu-prof src/worker.js # writes a .cpuprofile you can open in DevTools
kill -USR1 <pid> # start the inspector on an already-running processFull lesson: Errors, logging and debugging →
Testing with the built-in test runner
The first test
node --test # discover and run every matching file
node --test --watch # rerun on change
node --test test/unit/*.test.js
node --test --test-only # run just the tests marked with { only: true }
Watch, coverage and setup
node --test --test-concurrency=4
node --test --experimental-test-coverage --test-coverage-lines=80
node --test --test-name-pattern="checkout"
node --test --test-reporter=spec
node --test --test-reporter=junit --test-reporter-destination=report.xml
node --test --import ./test/setup.js # global hooks, tracing, env defaultsFull lesson: Testing with the built-in test runner →
Databases, HTTP clients and external services
Outbound HTTP with fetch
const res = await fetch("https://api.example.com/orders/1042", {
signal: AbortSignal.timeout(2000), // Node 17.3+
headers: { accept: "application/json" },
});
if (!res.ok) {
const detail = (await res.text()).slice(0, 200);
throw new Error("upstream " + res.status + ": " + detail);
}
const order = await res.json();Full lesson: Databases, HTTP clients and external services →
Worker threads, child processes and clustering
child_process
import { spawn, execFile } from "node:child_process";
import { promisify } from "node:util";
import { pipeline } from "node:stream/promises";
// spawn streams output — the safe default for long or large jobs
const child = spawn("ffmpeg", ["-i", "in.mp4", "out.webm"], {
stdio: ["ignore", "pipe", "pipe"],
});
await pipeline(child.stdout, process.stdout);
// execFile runs a known binary with an argument array and captures the output
const { stdout } = await promisify(execFile)("git", ["rev-parse", "HEAD"]);Full lesson: Worker threads, child processes and clustering →
Security, performance and deployment
Limits and dependencies
// fixed-window limiter backed by Redis
async function allow(key, limit = 100, windowSeconds = 60) {
const bucket = "rl:" + key + ":" + Math.floor(Date.now() / 1000 / windowSeconds);
const count = await redis.incr(bucket);
if (count === 1) await redis.expire(bucket, windowSeconds);
return count <= limit;
}
res.setHeader("ratelimit-limit", "100");
res.setHeader("ratelimit-remaining", String(Math.max(0, limit - used)));
Profiling and deployment
node --cpu-prof --cpu-prof-dir=./prof src/server.js
node --heap-prof src/server.js
node --max-old-space-size=512 src/server.js # cap the heap below the container limit
node --report-on-signal --report-signal=SIGUSR2 src/server.jsFull lesson: Security, performance and deployment →
FAQ
Is this Node.js cheat sheet free to use?
Where do the examples come from?
How do I go deeper than a cheat sheet?
Related cheat sheets
PHP Java HTTP Go Rust Spring Boot
Last refreshed 2026-09-27.