Node.js

Complete Node.js cheat sheet covering the runtime, modules, fs, streams, Express, authentication patterns, and security best practices.

11 sections30 cards

Node.js is a JavaScript runtime built on Chrome's V8 engine. It lets you run JS outside the browser — on a server, your machine, scripts, etc.

Key characteristics: single-threaded, non-blocking I/O, event-driven. It doesn't wait for disk reads or network calls — it registers a callback and moves on. This makes it very efficient for I/O-heavy tasks (APIs, file servers, real-time apps) but not great for CPU-heavy tasks (image processing, ML inference) which block the thread.

The event loop in Node works the same way as in the browser but has extra phases: timers → pending callbacks → idle/prepare → poll (I/O) → check (setImmediate) → close callbacks. process.nextTick() fires before any phase transition — highest priority after current operation.

setImmediate(fn) — fires in the check phase, after I/O. setTimeout(fn, 0) — fires in timers phase. In I/O context, setImmediate always fires before setTimeout(fn, 0).

CommonJS (CJS)

Default in Node. Files use .js or .cjs.

require("module") — import. Synchronous. Cached after first load.

module.exports = value — export a single value.

exports.name = value — shorthand. exports is a reference to module.exports. Don't reassign exports directly — breaks the reference.

__dirname — absolute path to current file's directory.

__filename — absolute path to current file.

Modules are wrapped in a function: (function(exports, require, module, __filename, __dirname) { ... }) — that's why these variables exist.

ES Modules (ESM)

Modern standard. Use .mjs extension or set "type": "module" in package.json.

import fs from "fs" — default import.

import { readFile } from "fs/promises" — named import.

export default fn / export const name = val

Top-level await is supported in ESM.

No __dirname / __filename in ESM. Workaround: import.meta.url then use fileURLToPath + path.dirname.

CJS and ESM don't mix easily. Most modern projects use ESM. Express and many packages still default to CJS.

fs — file system

fs.readFile(path, "utf8", callback) — async read. Callback-based.

fs.promises.readFile(path, "utf8") — promise-based. Prefer this.

fs.readFileSync(path, "utf8") — sync. Blocks the event loop. Use only in startup/scripts.

fs.writeFile(path, data, callback) — write (creates or overwrites).

fs.appendFile(path, data) — append to file.

fs.unlink(path) — delete file.

fs.mkdir(path, { recursive: true }) — create directory. recursive creates parents.

fs.readdir(path) — list directory contents.

fs.stat(path) — file metadata (size, modified time, isFile, isDirectory).

fs.rename(oldPath, newPath) — move/rename.

fs.watch(path, callback) — watch for changes.

fs.createReadStream(path) / fs.createWriteStream(path) — stream large files.

path

path.join("/users", "name", "file.txt") — join segments with OS separator. Handles double slashes, dots.

path.resolve("./file.txt") — absolute path from cwd.

path.basename("/users/file.txt")"file.txt"

path.dirname("/users/file.txt")"/users"

path.extname("file.txt")".txt"

path.parse("/users/file.txt"){ root, dir, base, ext, name }

path.sep — OS separator (/ or \).

Always use path.join for file paths — never string concatenation. Handles OS differences.

os

os.platform()"linux", "darwin", "win32"

os.cpus() — array of CPU info. os.cpus().length for core count.

os.totalmem() / os.freemem() — memory in bytes.

os.homedir() — user's home directory.

os.tmpdir() — temp directory.

os.hostname() — machine hostname.

os.EOL — line ending (\n or \r\n).

events

EventEmitter — base class for all Node event-driven objects (streams, HTTP server, etc.).

emitter.on(event, listener) — subscribe. Alias: addListener.

emitter.once(event, listener) — subscribe for one event only.

emitter.emit(event, ...args) — fire event synchronously.

emitter.off(event, listener) — unsubscribe. Alias: removeListener.

emitter.removeAllListeners(event)

emitter.listeners(event) — array of listeners.

Extend EventEmitter: class MyClass extends EventEmitter {} — your class gets all emitter methods.

Default max listeners = 10. Increase with emitter.setMaxListeners(n) or you'll get a warning.

http / https

http.createServer((req, res) => { ... }) — raw HTTP server. Most projects use Express instead.

req.method, req.url, req.headers — request info.

Read body: req is a stream. Listen to data and end events, or use async...for await (const chunk of req).

res.writeHead(statusCode, headers) — set status and headers.

res.setHeader(name, value)

res.write(data) — send partial response.

res.end(data) — send final response and close connection.

server.listen(port, callback) — start listening.

other useful modules

crypto — hashing, encryption. crypto.createHash("sha256").update(data).digest("hex")

urlnew URL(urlString) parses URLs. Access .pathname, .searchParams, etc.

querystring / URLSearchParams — parse query strings.

util.promisify(fn) — convert callback-based function to promise-based.

util.inspect(obj) — deep-print object for debugging.

child_process.exec(cmd, cb) — run shell command. spawn for streaming output.

worker_threads — true multi-threading for CPU work.

cluster — fork multiple processes sharing a port. Old way to use multiple cores.

buffer — binary data. Buffer.from("hello"), Buffer.alloc(size).

Streams handle data in chunks rather than loading it all in memory. Essential for large files, real-time data, HTTP responses.

4 types: Readable (source), Writable (destination), Duplex (both), Transform (duplex that modifies data).

Readable events: data (chunk arrives), end (no more data), error.

Writable methods: write(chunk), end(). Events: drain (can write again), finish, error.

readable.pipe(writable) — connect streams. Handles backpressure automatically. E.g. fs.createReadStream(file).pipe(res) — stream file to HTTP response.

pipeline(stream1, stream2, ..., callback) from stream module — like pipe but properly handles errors and cleanup. Use this over raw .pipe().

Backpressure: when writable is slower than readable, writable buffers data. write() returns false when buffer full — pause readable until drain event.

process.env — environment variables. process.env.PORT, process.env.NODE_ENV.

process.argv — command-line arguments array. [0] = node path, [1] = script path, [2+] = your args.

process.cwd() — current working directory (where you ran node from).

process.exit(0) — exit cleanly. Non-zero = error.

process.stdout.write() / process.stderr.write() — write to stdout/stderr directly.

process.on("uncaughtException", handler) — last resort error handler. Log and exit — don't try to continue.

process.on("unhandledRejection", handler) — unhandled promise rejections.

process.on("SIGTERM", handler) / process.on("SIGINT", handler) — graceful shutdown signals.

process.memoryUsage() — heap and RSS memory stats.

process.hrtime.bigint() — high-resolution timer for performance measurement.

package.json essentials

name, version, description, main (entry point), type ("module" for ESM).

scripts — run with npm run scriptname. Special: start, test, build don't need run.

dependencies — needed in production. Install: npm install pkg.

devDependencies — needed only in development (linting, testing, build tools). Install: npm install pkg --save-dev.

engines — specify required Node version. "engines": { "node": ">=18" }.

private: true — prevent accidental npm publish.

npm commands

npm init -y — create package.json with defaults.

npm install — install all dependencies from package.json.

npm install pkg@version — specific version.

npm uninstall pkg

npm update — update packages within version range.

npm outdated — show outdated packages.

npm list — show installed packages.

npm run script — run a script.

npx pkg — run package without installing globally.

npm audit — check for security vulnerabilities.

package-lock.json — exact dependency tree. Commit this. Never edit manually.

semver

Version format: MAJOR.MINOR.PATCH — e.g. 2.4.1.

MAJOR — breaking changes. MINOR — new features, backward compatible. PATCH — bug fixes.

^2.4.1 — allows MINOR and PATCH updates (2.x.x). Most common.

~2.4.1 — allows only PATCH updates (2.4.x).

2.4.1 — exact version only.

* or latest — any version.

Express is a minimal web framework for Node. Handles routing, middleware, request/response abstraction.

routing

app.get(path, handler) / .post / .put / .patch / .delete

app.all(path, handler) — all HTTP methods.

app.use(path, handler) — match any method, path prefix.

Route params: /users/:id — access via req.params.id.

Optional param: /users/:id?

Wildcard: /files/* — access via req.params[0].

Query string: /search?q=hello — access via req.query.q.

Router: express.Router() — modular routing. Mount with app.use("/api", router).

Route chaining: app.route("/users").get(fn).post(fn)

middleware

Middleware = function with signature (req, res, next). Runs in order. Must call next() to pass control or res.send() to respond.

Types: application-level (app.use(fn)), router-level (router.use(fn)), error-handling ((err, req, res, next)), built-in, third-party.

Built-in middleware:

express.json() — parse JSON request body. Populates req.body.

express.urlencoded({ extended: true }) — parse form data.

express.static("public") — serve static files from a directory.

Common third-party: cors, helmet, morgan (logging), compression, cookie-parser, multer (file uploads).

Error middleware — 4 params, must be registered last: app.use((err, req, res, next) => { res.status(500).json({ error: err.message }) })

Pass error to error handler: next(err). Skip to next route: next("route").

req & res

req.body — parsed request body (needs middleware).

req.params — route params.

req.query — query string params.

req.headers — request headers.

req.cookies — cookies (needs cookie-parser).

req.ip — client IP.

req.method — HTTP method.

req.path / req.url / req.originalUrl

res.status(code) — set status. Chainable.

res.json(obj) — send JSON. Sets Content-Type automatically.

res.send(data) — send string/buffer/object.

res.redirect(url) / res.redirect(301, url)

res.sendFile(path) — send a file.

res.setHeader(name, val) / res.cookie(name, val, opts)

res.locals — object to pass data between middleware in a request cycle.

express patterns

Controller pattern: route handlers in separate files. Route files just call controllers.

Async error handling: Express doesn't catch async errors automatically. Either wrap handlers: const asyncHandler = fn => (req, res, next) => fn(req, res, next).catch(next), or use express-async-errors package.

Environment config: app.set("trust proxy", 1) when behind reverse proxy (Nginx) to get real IP.

Port: const PORT = process.env.PORT || 3000

Graceful shutdown: listen for SIGTERM, stop accepting new connections, finish current requests, then exit.

JWT

JSON Web Token — stateless auth. Token has 3 parts: header.payload.signature. Signed with a secret.

jwt.sign(payload, secret, { expiresIn: "7d" }) — create token.

jwt.verify(token, secret) — verify and decode. Throws if invalid/expired.

Send in Authorization header: Bearer <token>. Read with: req.headers.authorization?.split(" ")[1].

Access token (short-lived, ~15min) + Refresh token (long-lived, ~7d) pattern — refresh token stored in httpOnly cookie, access token in memory.

JWT payload is base64 encoded, not encrypted — don't put sensitive data in it. Anyone can decode it.

passwords

Never store plain text passwords. Use bcrypt.

bcrypt.hash(password, saltRounds) — hash password. saltRounds = 10-12 for most apps.

bcrypt.compare(plainPassword, hash) — verify. Returns boolean.

Salt is automatically embedded in the hash — no need to store separately.

Higher salt rounds = more secure but slower. 10 rounds ≈ ~100ms which is fine.

auth middleware

Protect routes with middleware that verifies the token before the handler runs.

Pattern: extract token → verify → attach user to req.user → call next(). If invalid: res.status(401).json({ error: "Unauthorized" }).

Role-based: after auth middleware, check req.user.role in another middleware. Return 403 if insufficient.

httpOnly cookie — not accessible by JS. Prevents XSS token theft. Use for refresh tokens.

SameSite: "strict" / "lax" cookie — prevents CSRF attacks.

sessions

Alternative to JWT. Server stores session data, client gets a session ID cookie.

express-session — session middleware. Store in memory (dev only), Redis, or DB for production.

req.session.userId = user._id — set session data.

req.session.destroy() — logout.

Sessions are stateful — need a shared store if running multiple Node instances. JWT is stateless — scales easily.

Create a custom AppError class extending Error with a statusCode property. Throw these throughout the app. The error middleware reads the status code and sends appropriate response.

Operational errors (bad input, not found, unauthorized) vs programmer errors (bugs, unhandled edge cases). Operational errors → send to client. Programmer errors → log, restart process.

Validation: use zod or joi to validate request body before it hits your logic. Define schema, parse/validate, get typed safe data or throw validation error.

zod example: const schema = z.object({ email: z.string().email(), age: z.number().min(18) }); const data = schema.parse(req.body) — throws if invalid.

performance

Never block the event loop — no fs.readFileSync in request handlers, no heavy CPU work.

For CPU-intensive work: use worker_threads or offload to a separate service.

Use cluster module or PM2 to run multiple processes across CPU cores.

Enable compression middleware — gzip responses.

Use connection pooling in DB clients — don't create new connection per request.

Cache frequently accessed data in Redis. Avoid redundant DB queries.

Stream large responses instead of loading into memory.

--max-old-space-size=512 — set heap limit when running Node.

security essentials

helmet — sets security headers (X-Frame-Options, CSP, etc.). Always use.

cors — configure allowed origins. Don't use origin: "*" in production.

Rate limiting: express-rate-limit — limit requests per IP.

Input sanitization — strip/escape HTML from user input. Prevent XSS.

Parameterized queries or ORM — never string-interpolate user input into SQL/NoSQL queries. Prevent injection.

Don't expose stack traces in production error responses.

NODE_ENV=production — Express disables detailed error messages in production mode.

.env files — store secrets. Never commit to git. Use dotenv package to load them.

project structure

Common pattern:

src/routes/ — route definitions

src/controllers/ — request handlers

src/services/ — business logic

src/models/ — DB models

src/middleware/ — custom middleware

src/utils/ — helpers

src/config/ — DB connection, env config

Keep controllers thin — logic in services. Services don't know about req/res.

debugging &amp; logging

console.log/error/warn/info — basic. Use structured logging in production.

winston / pino — production loggers. JSON output, log levels, file transport.

morgan — HTTP request logger middleware for Express.

Node debugger: node --inspect app.js — open chrome://inspect to debug.

DEBUG=express:* env var — see internal Express debug logs.

PM2: process manager. pm2 start app.js, auto-restart on crash, log management, cluster mode.

common traps

Node is single-threaded — one uncaught exception can crash the entire server. Always handle errors.

Callback-based APIs don't throw — errors come as first argument. Forgetting to check err is a silent failure.

Memory leaks from: event listeners not removed, closures holding references, global caches without eviction.

require is cached — modifying module.exports after loading won't affect already-loaded consumers.

Circular dependencies in CJS — can result in empty exports. Redesign or use dynamic require.

Streaming vs buffering — for large files never use readFile, use streams.

things to know cold

Event loop order: nextTick → microtasks (promises) → timers → I/O → setImmediate → close.

Non-blocking I/O is handled by libuv thread pool (default 4 threads). CPU work is NOT offloaded.

Express middleware order matters — express.json() must come before routes that need req.body.

Async errors in Express don't auto-propagate — must call next(err).

CORS preflight — browser sends OPTIONS request before cross-origin POST. Your server must handle it.

res.json() automatically sets Content-Type: application/json. res.send() guesses.