Node.js Package Management & Supply-Chain

Node.js Package Management & Supply-Chain

Overview

This reference is the consumer side of the npm ecosystem: how you install, resolve, lock, and secure the dependencies a Node.js project pulls in. It covers the four mainstream package managers (npm, pnpm, Yarn Berry, bun), the lockfile + reproducible-install contract, workspaces/monorepos, semver resolution and override mechanics, npm scripts/lifecycle, and the supply-chain hardening surface (audit, provenance, install-script defenses, PM pinning).

It deliberately stops at three boundaries owned by sibling references:

The mental model: manifest (package.json) declares intent → resolver picks concrete versions → lockfile freezes them → installer materializes node_modules (or a PnP map) → lifecycle scripts run → audit/provenance/pinning guard the supply chain.

Core concepts

1. The package managers — install models that differ at the layout layer

All four read package.json, but they materialize dependencies very differently:

2. Lockfiles — the reproducibility contract

A lockfile pins the entire resolved tree (exact versions + resolved URLs + integrity hashes, typically SHA-512 / SRI) so a second install reproduces the first bit-for-bit. Each PM has its own:

PM Lockfile Notes
npm package-lock.json JSON; integrity field is the SRI hash verified on install.
pnpm pnpm-lock.yaml YAML; encodes the isolated layout + peer resolution.
Yarn yarn.lock Berry uses a YAML-ish format; Classic a custom one.
bun bun.lock (text) Replaced the binary bun.lockb in Bun 1.2; text diffs cleanly.

3. Workspaces / monorepos

A workspace is a repo of multiple packages sharing one install + one lock, with local packages linked to each other instead of being fetched from the registry.

4. Dependency resolution & semver

Ranges in package.json are semver (MAJOR.MINOR.PATCH); the resolver picks the highest published version satisfying every constraint, then dedupes shared transitive deps.

5. npm scripts & lifecycle

"scripts" in package.json defines named commands run via npm run <name>. npm auto-wraps any script with hooks: running <name> executes pre<name><name>post<name> in sequence (e.g. prebuild/build/postbuild).

6. Supply-chain security

The dependency graph is the largest untrusted attack surface in a Node app. The defenses:

Package-manager comparison

Dimension npm pnpm Yarn Berry (PnP) bun
Install model / layout Flat hoisted node_modules Content-addressable store + symlinked, isolated node_modules No node_modules.pnp.cjs resolution map (or nodeLinker: node-modules) Flat node_modules from a global cache
Store / cache Per-project copy (global cache _cacache) Global store, hard-linked (one copy/version on disk) Zipped caches; supports zero-installs Global cache w/ hardlink / copy-on-write
Lockfile package-lock.json pnpm-lock.yaml yarn.lock bun.lock (text)
Workspace config workspaces in package.json pnpm-workspace.yaml workspaces in package.json workspaces in package.json
Run across packages --workspace / --workspaces --filter / -r (graph-ordered) yarn workspace(s) / foreach --filter
peerDeps auto-install On (npm 7+) On (pnpm 8+, auto-install-peers) Off (warns) On (npm-compatible)
Override field overrides pnpm.overrides resolutions overrides (npm-compatible)
Phantom-dep strictness Loose (hoist exposes undeclared) Strict by construction Strict (“semantic erroring”) Loose (flat layout)
Frozen/CI install npm ci --frozen-lockfile --immutable --frozen-lockfile

Practical patterns

Anti-patterns

Troubleshooting

References