Installing Packages
npm install is the command you will run more than any other. On the surface it just downloads code, but underneath it resolves a dependency graph, deduplicates it into a flat tree, writes the lockfile, and runs lifecycle scripts. This page covers the everyday forms, the flags worth knowing, and what actually happens on disk.
The core forms
Command | What it does |
|---|---|
| Install everything in |
| Add a package to |
| Add to |
| Install globally onto your |
| Install a specific version or tag |
| Remove a package and update the lockfile |
npm install express # latest, into dependencies npm install jest --save-dev # dev tooling npm install typescript@5.4.0 # exact version npm install lodash@latest # the "latest" dist-tag npm install express@^4.18.0 # a range — resolved & pinned in the lock
What happens on disk
A single npm install express triggers a whole pipeline. Understanding it demystifies why the first install is slow and later ones are fast:
The install pipeline
1. Read package.json + package-lock.json 2. Build the dependency graph (express → its deps → their deps …) 3. Resolve every range to a concrete version (reuse lock if present) 4. Fetch tarballs (or reuse the global cache at ~/.npm/_cacache) 5. Verify each tarball against its integrity hash 6. Lay out a FLAT node_modules (hoist shared deps to the top) 7. Run lifecycle scripts (preinstall/install/postinstall) 8. Write the updated package.json + package-lock.json
The flat node_modules
Since npm 3, dependencies are hoisted: shared packages are flattened to the top of node_modules so they are not duplicated. Conflicting versions get nested. This is why node_modules is wide and shallow, not deeply nested:
Hoisting in action
Your app needs: A (needs lodash@4) B (needs lodash@4) C (needs lodash@3)
node_modules/
├─ A/
├─ B/
├─ lodash/ ← v4, hoisted, shared by A and B
└─ C/
└─ node_modules/
└─ lodash/ ← v3, nested because it conflictsUseful flags
Flag | Effect |
|---|---|
| Record under |
| Pin the exact version (no |
| Install to the global prefix, onto |
| Skip devDependencies (production install) |
| Install without touching package.json |
| Override caches and conflicting peer deps (use sparingly) |
| Ignore peer-dependency conflicts (npm 7+ escape hatch) |
Production installs
On a server or in a container, you want runtime deps only — fast, reproducible, no build tooling:
npm ci --omit=dev # lockfile-exact, runtime deps only # (older syntax: npm ci --production)
Verifying what you got
npm ls # the installed top-level tree npm ls <pkg> # where a specific package resolved npm outdated # which packages have newer versions npm audit # known vulnerabilities in the tree