npm is the default package manager for Node.js and the worldβs largest software registry. Whether youβre installing a single dependency or managing a monorepo with workspaces, npm is the tool youβll use daily. This guide covers everything from basics to advanced workflows.
For quick reference, keep the npm cheat sheet open. If youβre deciding between package managers, check our npm vs pnpm vs yarn comparison.
Installing Node.js and npm
npm comes bundled with Node.js. The recommended way to install is through a version manager:
# Using nvm (recommended)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
nvm install --lts
nvm use --lts
# Verify
node --version
npm --version
Using nvm lets you switch between Node versions per project. Create a .nvmrc file in your project root:
20.11.0
Then nvm use automatically picks the right version.
Package.json Essentials
Every Node.js project starts with package.json:
npm init -y # creates package.json with defaults
Key fields:
{
"name": "my-app",
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "node --watch src/index.js",
"build": "tsc",
"start": "node dist/index.js",
"test": "vitest"
},
"dependencies": {
"express": "^4.18.0"
},
"devDependencies": {
"typescript": "^5.4.0"
}
}
The type: "module" field enables ES modules (import/export) instead of CommonJS (require).
Installing Packages
# Add a dependency
npm install express
# Add a dev dependency
npm install -D typescript vitest
# Install a specific version
npm install react@18.2.0
# Install all dependencies from package.json
npm install
# Install globally (for CLI tools)
npm install -g vercel
Version Ranges
^4.18.0β compatible with 4.x.x (most common, default)~4.18.0β patch updates only: 4.18.x4.18.0β exact version, no updates*β any version (dangerous, avoid)
The Lock File
package-lock.json pins exact versions of every dependency (including transitive ones). Always commit it:
# Regenerate lock file
rm package-lock.json && npm install
# Install exactly what's in the lock file (CI/CD)
npm ci
npm ci is faster than npm install for CI because it skips resolution and installs directly from the lock file.
npm Scripts
Scripts are the standard way to define project commands:
{
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "eslint .",
"format": "prettier --write .",
"typecheck": "tsc --noEmit",
"precommit": "npm run lint && npm run typecheck"
}
}
Run them with npm run <name>. Special scripts start, test, and stop donβt need run:
npm start # same as npm run start
npm test # same as npm run test
npm run dev # custom scripts need 'run'
Pre/Post Hooks
{
"scripts": {
"prebuild": "rm -rf dist",
"build": "tsc",
"postbuild": "cp package.json dist/"
}
}
prebuild runs automatically before build, and postbuild runs after.
Workspaces (Monorepos)
npm workspaces let you manage multiple packages in one repo:
{
"workspaces": ["packages/*", "apps/*"]
}
my-monorepo/
βββ package.json
βββ packages/
β βββ shared/
β β βββ package.json
β βββ ui/
β βββ package.json
βββ apps/
βββ web/
βββ package.json
Commands:
# Install all workspace dependencies
npm install
# Run a script in a specific workspace
npm run build -w packages/shared
# Run a script in all workspaces
npm run build --workspaces
# Add a dependency to a specific workspace
npm install zod -w apps/web
Updating Dependencies
# Check for outdated packages
npm outdated
# Update within version ranges
npm update
# Update a specific package
npm install express@latest
# Interactive update tool
npx npm-check-updates -i
For major version bumps, use npm-check-updates:
npx npm-check-updates -u # updates package.json
npm install # installs new versions
Security
# Check for known vulnerabilities
npm audit
# Auto-fix what's possible
npm audit fix
# Force fix (may include breaking changes)
npm audit fix --force
Publishing Packages
# Login to npm
npm login
# Publish (public by default for unscoped packages)
npm publish
# Publish a scoped package publicly
npm publish --access public
# Bump version and publish
npm version patch # 1.0.0 β 1.0.1
npm version minor # 1.0.0 β 1.1.0
npm version major # 1.0.0 β 2.0.0
npm publish
Use .npmignore or the files field in package.json to control what gets published.
Troubleshooting Common Errors
npm errors are notoriously cryptic. Here are the most common ones with links to detailed fixes:
- ERESOLVE dependency conflict β peer dependency version mismatch. See npm ERESOLVE fix
- EACCES permission denied β global install without permissions. See npm EACCES fix
- Cannot find module β package not installed or wrong path. See npm cannot find module fix
- E404 package not found β typo in package name or private registry issue. See npm E404 fix
- ENOENT β missing file during install. See npm ENOENT fix
- ELIFECYCLE β script failed during install. See npm ELIFECYCLE fix
- Peer dependency errors β see npm peer dependency fix
- Deprecated warnings β see npm deprecated fix
The nuclear option for most npm issues:
rm -rf node_modules package-lock.json
npm cache clean --force
npm install
npx β Run Packages Without Installing
npx runs a packageβs binary without installing it globally:
# Run a one-off command
npx create-next-app my-app
npx astro add tailwind
npx tsc --init
# Run a specific version
npx tsx@latest script.ts
# Run from a GitHub repo
npx github:user/repo
npx first checks if the package is installed locally, then globally, then downloads it temporarily. Itβs perfect for scaffolding tools and one-off commands.
.npmrc Configuration
Project-level npm config goes in .npmrc:
# Use exact versions by default
save-exact=true
# Set a default registry (for private packages)
registry=https://registry.npmjs.org/
# Scoped registry
@mycompany:registry=https://npm.mycompany.com/
# Auto-install peer dependencies
legacy-peer-deps=false
# Engine strictness
engine-strict=true
Engines and Node Version
Lock down which Node.js versions your project supports:
{
"engines": {
"node": ">=20.0.0",
"npm": ">=10.0.0"
}
}
Combined with engine-strict=true in .npmrc, npm will refuse to install if the Node version doesnβt match.
Overrides (Dependency Patching)
Force a specific version of a transitive dependency:
{
"overrides": {
"lodash": "4.17.21",
"some-package>vulnerable-dep": "2.0.0"
}
}
This is useful for fixing security vulnerabilities in dependencies you donβt control directly.
Private Packages and Scopes
Scoped packages are namespaced under an organization:
# Create a scoped package
npm init --scope=@mycompany
# Publish privately (requires npm paid plan)
npm publish
# Publish scoped package publicly
npm publish --access public
For private registries (Artifactory, Verdaccio, GitHub Packages):
# .npmrc
@mycompany:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
Lifecycle Hooks and Custom Scripts
npm supports lifecycle hooks that run at specific points:
{
"scripts": {
"prepare": "husky install",
"preinstall": "npx only-allow pnpm",
"postinstall": "prisma generate",
"prepack": "npm run build",
"prepublishOnly": "npm run test && npm run build"
}
}
Key hooks:
prepareβ runs afternpm installand beforenpm publish(great for build steps and git hooks)preinstallβ runs before installing dependencies (useful to enforce a specific package manager)postinstallβ runs after all dependencies are installed (code generation, native builds)prepublishOnlyβ runs beforenpm publish(validation, tests)
Environment Variables in Scripts
Pass environment variables to npm scripts:
{
"scripts": {
"dev": "NODE_ENV=development node src/index.js",
"build": "NODE_ENV=production node build.js",
"start:staging": "PORT=4000 node dist/index.js"
}
}
For cross-platform compatibility (Windows doesnβt support inline env vars), use cross-env:
{
"scripts": {
"build": "cross-env NODE_ENV=production node build.js"
}
}
Debugging npm Issues
When things go wrong:
# Verbose output
npm install --verbose
# Check where npm looks for packages
npm root
npm root -g
# See the full dependency tree
npm ls
npm ls express
# Check why a package is installed
npm explain express
# See npm's config
npm config list
Common Mistakes
Not committing the lock file. package-lock.json ensures everyone gets the same dependency versions. Without it, npm install might resolve different versions on different machines.
Using npm install in CI. Use npm ci instead β itβs faster, stricter, and installs exactly whatβs in the lock file. npm install can modify the lock file.
Installing everything globally. Global installs (npm install -g) should only be for CLI tools you use across projects. For project dependencies, always install locally.
Ignoring peer dependency warnings. Peer dependency mismatches cause subtle bugs. Fix them by installing the correct version or using overrides to force a specific version.
Not using --save-exact. Version ranges (^4.18.0) can lead to different versions across installs. For maximum reproducibility, use npm install --save-exact or set save-exact=true in .npmrc.
npm vs Alternatives
npm isnβt the only option. pnpm uses a content-addressable store that saves disk space and is stricter about dependencies. Yarn offers plugβnβplay for zero-install setups. Bun has its own package manager thatβs significantly faster.
For a detailed comparison, see npm vs pnpm vs yarn and pnpm vs npm.
Next Steps
- Grab the npm cheat sheet for daily reference
- Explore pnpm if you want stricter dependency management
- Check out Deno for a modern Node.js alternative
- Learn TypeScript to add type safety to your Node.js projects