npm has been JavaScript’s dominant package manager for over a decade, and understanding its lockfile and versioning behavior prevents a lot of “works on my machine” frustration.
npm init -y npm install express npm install --save-dev jest # package-lock.json pins EXACT versions of every dependency (and sub-dependency) # always commit it - it's what makes builds reproducible across machines npm ci # installs exactly from the lockfile, faster and stricter than npm install npm outdated # see what's behind npm update # update within the semver ranges in package.json npm audit # check for known security vulnerabilities
Semver Ranges in package.json
{
"dependencies": {
"express": "^4.18.0", // ^ allows minor/patch updates: 4.x.x, not 5.x.x
"lodash": "~4.17.21", // ~ allows only patch updates: 4.17.x
"left-pad": "1.3.0" // exact version, no updates without editing this file
}
}
npm ci (not npm install) is what CI pipelines should use — it fails fast if package-lock.json is out of sync with package.json, rather than silently resolving new versions.