npm 101: Managing JavaScript Dependencies

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.