Claude
Skills
Sign in
Back

nodejs

Included with Lifetime
$97 forever

Node.js runtime conventions, APIs, and ecosystem patterns. Invoke whenever task involves any interaction with Node.js runtime — server code, CLI tools, scripts, module system, streams, process lifecycle, or package configuration.

Backend & APIs

What this skill does


# Node.js

**Respect the event loop. Every blocking operation is a scalability bug.**

Node.js rewards async-first, stream-oriented code. If your Node.js code fights the event loop, it's wrong.

## References

- **Module system** — [`${CLAUDE_SKILL_DIR}/references/modules.md`]: ESM/CJS comparison tables, file extension rules,
  conditional exports patterns
- **Event loop** — [`${CLAUDE_SKILL_DIR}/references/event-loop.md`]: Phase order, execution priority, blocking
  operations table, worker pool
- **Streams** — [`${CLAUDE_SKILL_DIR}/references/streams.md`]: Stream types table, pipeline patterns, backpressure
  details
- **Error handling** — [`${CLAUDE_SKILL_DIR}/references/errors.md`]: Error categories table, global handlers,
  centralized error handling
- **Security** — [`${CLAUDE_SKILL_DIR}/references/security.md`]: Supply chain threats table, HTTP security headers,
  process hardening

## Module System

- **Use ESM.** Set `"type": "module"` in `package.json`. Use `.mjs`/`.cjs` only when mixing module systems within one
  package.
- **Use `node:` prefix** for all built-in imports: `import fs from 'node:fs'`. Prevents package name collision attacks
  and is unambiguous.
- **Import `process` explicitly.** `import process from 'node:process'`. Never rely on the `process` global — explicit
  imports make dependencies visible.
- **Define `"exports"` in `package.json`** for libraries. Encapsulates internals — only paths listed in `"exports"` are
  importable by consumers.
- **Imports at module top.** No dynamic `import()` for statically-known dependencies.
- **Use `import.meta.dirname`/`import.meta.filename`** instead of `__dirname`/`__filename`.
- **Use `import.meta.resolve()`** instead of `require.resolve()` in ESM.
- **Always set `"type"` explicitly** in `package.json`, even in CJS packages — future-proofs the package and helps
  tooling.
- **Use conditional exports** for dual CJS/ESM packages. Order: `types` > `import` > `require` > `default`. Always
  include `"default"` as fallback.
- **Place `"types"` first** in conditional exports when publishing TypeScript declarations.
- **Use `#` imports** (`"imports"` in `package.json`) for clean internal paths without `../../../`. Supports conditional
  resolution for platform-specific implementations.
- **Keep `"main"` alongside `"exports"`** only for backward compatibility with old Node.js or bundlers. Never use
  `"main"` alone for new packages.
- **JSON imports in ESM** require `with { type: 'json' }` attribute.
- **Use `createRequire()`** from `node:module` only when you must `require()` in ESM (e.g., native addons).
- **CJS interop:** default import from CJS always works. Named imports work if CJS uses static export patterns;
  otherwise destructure the default.
- **`require()` can load synchronous ESM** (no top-level `await`). For ESM with top-level `await`, use dynamic
  `import()`.
- **Self-referencing:** a package can import its own exports by name when `"exports"` is defined.

File extension rules and ESM vs CJS comparison tables: see `${CLAUDE_SKILL_DIR}/references/modules.md`.

## Event Loop

Node.js uses a single-threaded event loop for JavaScript and a libuv worker pool for expensive I/O and CPU tasks.

### Core Rules

- **Never block the event loop.** No sync I/O in servers (`readFileSync`, `execSync`, `crypto.pbkdf2Sync`,
  `zlib.inflateSync`). Sync APIs are acceptable only in CLI scripts, startup code, or build tools.
- **Offload CPU-intensive work** to `worker_threads` or child processes. For main-thread CPU work, partition into chunks
  with `setImmediate()` between iterations.
- **Bound input sizes.** Unbounded `JSON.parse`, `JSON.stringify`, regex, or iteration = DoS vector. A 50MB JSON string
  blocks the loop for ~2 seconds.
- **Avoid vulnerable regex.** No nested quantifiers `(a+)*`, no overlapping alternations `(a|a)*`, no backreferences
  with repetition. Use `safe-regex2`, RE2, or `indexOf`.
- **Prefer `setImmediate()` over recursive `process.nextTick()`.** `nextTick` starves I/O if called recursively. Use
  `nextTick` only when you must run before any I/O in the current tick (e.g., emitting events after construction before
  listeners attach).
- **Prefer `queueMicrotask()`** over `process.nextTick()` for new code — it's cross-platform and web-standard.
- **Inside I/O callbacks, `setImmediate` always fires before `setTimeout(fn, 0)`.** Outside I/O, the order is
  non-deterministic — do not depend on it.
- **Use `AbortController`** for cancellable timers and operations.

Phase order, execution priority, blocking operations table, and worker pool details: see
`${CLAUDE_SKILL_DIR}/references/event-loop.md`.

## Streams

Streams process data incrementally — use them for large files, HTTP bodies, data transformation pipelines, and proxying.
Do not use streams when data is already fully in memory.

### Core Rules

- **Use `pipeline()`** from `node:stream/promises` for stream composition. Never manual `.pipe()` chains — they don't
  propagate errors or handle cleanup.
- **Respect backpressure.** Check `.write()` return value; wait for `'drain'` event before continuing. `pipeline()`
  handles this automatically.
- **Prefer `Readable.from()`** for converting iterables/async iterables to streams.
- **Use async iteration** (`for await (const chunk of stream)`) as the simplest way to consume readable streams.
  Backpressure is handled automatically.
- **Use `readline` with `createInterface`** for line-by-line file processing.
- **`highWaterMark`** defaults to 16 KiB for byte streams, 16 objects for object mode. It's a hint, not a hard limit.
- **Object mode streams** count objects (not bytes) against `highWaterMark`. Enable with `{ objectMode: true }`.
- **Destroy streams explicitly** when you need to abort: `stream.destroy(new Error('msg'))`.
- **Custom Readable:** prefer async generators with `Readable.from()` over class-based `_read()` implementation unless
  you need fine-grained control.
- **Custom Transform:** implement `_transform(chunk, encoding, callback)` and optionally `_flush(callback)` for
  end-of-stream processing.
- **Custom Writable:** implement `_write(chunk, encoding, callback)` and optionally `_final(callback)` for cleanup
  before `'finish'` event.

Stream types table and `.pipe()` pitfalls: see `${CLAUDE_SKILL_DIR}/references/streams.md`.

## Error Handling

### Core Rules

- **Use `async`/`await` with `try`/`catch`.** No callbacks for new code.
- **Always `return await`** when returning promises from `try` blocks — preserves full stack traces and ensures `catch`
  fires for rejections.
- **Extend `Error`.** Custom errors must extend `Error`, set a `code` property for programmatic matching (not message
  strings, which change), and set `name`.
- **Use `error.cause`** for chaining: `new Error("context", { cause: originalErr })`. The full chain is visible via
  `util.inspect()` and structured loggers.
- **Match errors by `error.code` or `instanceof`**, never by message string.
- **Register global handlers.** Always handle `process.on('unhandledRejection')` and `process.on('uncaughtException')`.
  Log, clean up, exit. Since Node.js 15+, unhandled rejections crash the process by default.
- **Never resume after `uncaughtException`.** The process state is unknown — log, cleanup, exit.
- **Subscribe to `'error'` events** on all EventEmitters and streams. An unhandled `'error'` event crashes the process.
  `pipeline()` handles stream errors automatically.
- **Handle `process.on('warning')`** for non-fatal process warnings (deprecations, memory leaks, experimental features).
- **Use centralized error handlers.** Don't scatter error handling across every middleware. Use a single error handler
  that maps error types to HTTP responses without leaking internals.
- **Handle once: log OR throw, not both.** `catch (e) { log(e); throw e }` causes duplicate logging.
- **Never swallow errors** in event handlers — always re-emit or log.

Error categories table and operational vs progra

Related in Backend & APIs