Skip to content

npm & Package Management

beginner2 min read
  • Node.js

How npm installs and version-controls the third-party packages a project depends on, using package.json to declare them and a lock file to make installs reproducible.

What you'll understand

  • What a package manager does and why projects need one
  • The roles of package.json, the lock file, and node_modules
  • How version ranges keep installs both fresh and reproducible

Explanation

npm is the default package manager for Node.js. A package manager downloads the third-party code your project depends on, records exactly what you asked for, and installs the right versions on any machine. Without one, you would copy libraries by hand and hope everyone on the team had the same versions.

Two files do the bookkeeping. package.json is the manifest you edit: it lists your dependencies and the version ranges you accept, plus scripts and project metadata. The lock file (package-lock.json) is generated, not edited: it pins the exact version of every package, including dependencies of your dependencies, so an install is reproducible. The installed code itself lands in the node_modules folder, which is disposable and never committed.

Versions follow semantic versioning (semver): MAJOR.MINOR.PATCH. A range like ^1.4.0 accepts new minor and patch releases but not a breaking major bump. This lets you pick up bug fixes automatically while the lock file guarantees that a given commit always installs the identical tree, so "works on my machine" stops being a mystery.

Examples

A minimal package.json declares dependencies and a start script:

{
  "name": "my-api",
  "version": "1.0.0",
  "scripts": {
    "start": "node dist/main.js"
  },
  "dependencies": {
    "express": "^4.19.2"
  }
}

These commands add a runtime dependency, add a dev-only dependency, and reproduce the exact locked tree in CI:

npm install express          # add a runtime dependency
npm install --save-dev jest   # add a dev dependency
npm ci                        # clean, reproducible install from the lock file

Common mistakes

  • Committing node_modules instead of committing the lock file.
  • Deleting or ignoring package-lock.json, which makes installs non-reproducible.
  • Putting build- or test-only tools in dependencies instead of devDependencies.
  • Editing files inside node_modules; changes there are wiped on the next install.

Best practices

  • Commit the lock file so every environment installs the same versions.
  • Use npm ci in automation for a clean, deterministic install.
  • Separate runtime dependencies from devDependencies deliberately.
  • Review and update dependencies regularly to pick up security fixes.

Further reading

Related topics