Reproducible installs prevent "works on my machine" bugs and surprise breakages from transitive updates.
npm packages use semantic versioning: MAJOR.MINOR.PATCH. A major bump may break you, a minor adds features compatibly, a patch only fixes bugs. In package.json you declare a range, not an exact version:
^1.4.2— any1.x.yat or above 1.4.2 (the default npm writes). For0.xversions the caret is stricter:^0.4.2means>=0.4.2 <0.5.0.~1.4.2— patch updates only:>=1.4.2 <1.5.0.1.4.2— exactly that version.
Ranges alone would make every install potentially different, which is why npm writes package-lock.json. It records the exact version, download URL and integrity hash of every package in the tree, including dependencies of dependencies. Commit it.
Two install commands, two purposes:
npm install— resolves ranges, may update the lockfile. Use it when adding or upgrading packages.npm ci— installs exactly what the lockfile says, deletes node_modules first, and fails if package.json and the lockfile disagree. Use it in CI and Docker builds.
To maintain dependencies:
npm outdatedlists packages with newer versions.npm updateupgrades within your ranges; a major upgrade needsnpm install pkg@latestand reading the changelog.npm ls pkgshows why a package is in your tree.npm auditreports known vulnerabilities.
Every dependency is code you run with full privileges. Prefer fewer, well-maintained packages, and check the weekly downloads and maintenance status before adding one.
Example
# package.json ranges
# "express": "^5.1.0" -> 5.1.0 up to (not including) 6.0.0
# "zod": "~3.24.1" -> 3.24.x only
# "uuid": "11.0.3" -> exactly 11.0.3
npm install # resolve ranges, update package-lock.json if needed
git add package.json package-lock.json
# In CI / Dockerfile: exact, clean, fails on mismatch
npm ci
npm outdated # what is behind?
npm install express@latest # deliberate major upgrade
npm ls debug # which packages pull in 'debug'?
npm audit # known vulnerabilitiesRemember
^allows minor and patch updates,~only patches- Commit package-lock.json; it pins the whole dependency tree
- Use
npm cifor reproducible builds
Watch out for
- Deleting package-lock.json to "fix" an install, which silently upgrades the whole tree.