Every npm command you reach for daily — and the ones you always have to look up. This covers init, install, scripts, versioning, workspaces, and publishing.
Quick reference
The 25 commands that cover 95% of daily npm work.
| Command | What it does |
|---|---|
npm init -y |
Create package.json with defaults |
npm install |
Install all dependencies |
npm install <pkg> |
Install and add to dependencies |
npm install -D <pkg> |
Install as devDependency |
npm install -g <pkg> |
Install globally |
npm install --save-exact <pkg> |
Install exact version (no ^) |
npm uninstall <pkg> |
Remove package |
npm update |
Update packages to latest semver |
npm update <pkg> |
Update one package |
npm outdated |
List outdated packages |
npm run <script> |
Run a script from package.json |
npm test |
Shorthand for npm run test |
npm start |
Shorthand for npm run start |
npm ci |
Clean install from lockfile |
npm list |
List installed packages |
npm list --depth=0 |
List top-level packages only |
npm info <pkg> |
Show package metadata |
npm view <pkg> versions |
List all published versions |
npm audit |
Check for vulnerabilities |
npm audit fix |
Auto-fix vulnerabilities |
npm dedupe |
Remove duplicate packages |
npm cache clean --force |
Clear npm cache |
npm login |
Authenticate with npm registry |
npm publish |
Publish package to registry |
npx <cmd> |
Run a package without installing |
Initialisation
Create a new project
# Interactive (asks questions)
npm init
# Accept all defaults
npm init -y
# With a template
npm init react-app my-app
npm init vite@latest my-app -- --template react-ts
package.json key fields
{
"name": "my-package",
"version": "1.0.0",
"description": "What it does",
"main": "index.js", // CJS entry point
"module": "dist/index.mjs", // ESM entry point
"exports": { // Modern entry point map
".": {
"import": "./dist/index.mjs",
"require": "./dist/index.cjs"
}
},
"scripts": {
"build": "tsc",
"dev": "nodemon src/index.js",
"test": "jest",
"lint": "eslint src/"
},
"keywords": ["node", "utility"],
"author": "Your Name <you@example.com>",
"license": "MIT",
"dependencies": {},
"devDependencies": {},
"peerDependencies": {}, // Required by consumer, not bundled
"engines": {
"node": ">=18.0.0",
"npm": ">=9.0.0"
},
"files": ["dist/", "README.md"], // Files included in npm publish
"private": true // Prevents accidental publish
}
Installing packages
Install commands
# Add to dependencies (default)
npm install express
npm install express@4.18.2 # Exact version
npm install express@latest # Latest tag
# Add to devDependencies
npm install -D typescript jest
npm install --save-dev typescript
# Add to peerDependencies (manually edit package.json, or)
npm install --save-peer react
# Install globally (CLI tools)
npm install -g typescript
npm install -g @vue/cli
# Install from various sources
npm install ./local-package # Local folder
npm install github:user/repo # GitHub repo
npm install github:user/repo#branch # Specific branch/commit
npm install https://example.com/pkg.tgz # Tarball URL
Install flags
| Flag | Short | What it does |
|---|---|---|
--save-dev |
-D |
Add to devDependencies |
--save-exact |
-E |
Save exact version (no ^) |
--global |
-g |
Install globally |
--no-save |
Install but don't update package.json | |
--dry-run |
Show what would be installed | |
--prefer-offline |
Use cache if available | |
--legacy-peer-deps |
Ignore peer dependency conflicts | |
--force |
-f |
Force re-fetch even if cached |
npm ci vs npm install
# Development: use npm install
# — updates package-lock.json
# — allows semver range resolution
npm install
# CI/CD and production: use npm ci
# — requires package-lock.json
# — deletes node_modules first
# — fails if lock file doesn't match package.json
# — never writes to package.json or lock file
npm ci
Removing and updating packages
# Remove a package
npm uninstall express
npm uninstall -g typescript # Remove global package
# Update packages
npm update # All packages within semver range
npm update express # One package
npm update --save # Also update version in package.json
# Check what's outdated
npm outdated
# Example output:
# Package Current Wanted Latest Location
# express 4.17.0 4.18.2 4.18.2 my-app
# Update to latest (bypasses semver range)
npm install express@latest
Interactive update (using npm-check)
npx npm-check -u # Interactive UI to choose updates
npx ncu -u # npm-check-updates: update package.json ranges
npm scripts
Defining scripts
{
"scripts": {
"build": "tsc --project tsconfig.json",
"dev": "nodemon --exec ts-node src/index.ts",
"test": "jest --coverage",
"test:watch": "jest --watch",
"lint": "eslint src/ --ext .ts",
"format": "prettier --write src/",
"clean": "rm -rf dist/",
"prepare": "husky install"
}
}
Running scripts
npm run build
npm run test:watch
# Shorthand aliases (no 'run' needed)
npm test # = npm run test
npm start # = npm run start
npm stop # = npm run stop
npm restart # = npm run restart
# Pass arguments to the script with --
npm run test -- --watchAll=false
npm run build -- --sourceMap
Pre and post hooks
npm auto-runs pre<script> before and post<script> after any script.
{
"scripts": {
"prebuild": "npm run clean",
"build": "tsc",
"postbuild": "node scripts/copy-assets.js",
"pretest": "npm run lint",
"test": "jest",
"prepare": "husky install" // Runs after npm install
}
}
Sequential and parallel scripts
{
"scripts": {
// Sequential (one after another)
"build:all": "npm run build:client && npm run build:server",
// Parallel (both at same time) — requires npm-run-all or &
"dev": "npm-run-all --parallel dev:client dev:server",
"dev:unix": "npm run dev:client & npm run dev:server",
// Cross-platform parallel
"dev:cross": "concurrently \"npm run dev:client\" \"npm run dev:server\""
}
}
Install helpers: npm install -D npm-run-all concurrently
Environment variables in scripts
{
"scripts": {
"build": "NODE_ENV=production tsc",
"build:win": "set NODE_ENV=production && tsc",
"build:cross": "cross-env NODE_ENV=production tsc"
}
}
npm install -D cross-env for cross-platform compatibility.
Semantic versioning
npm uses semver for version ranges.
Version range syntax
| Specifier | Example | Matches |
|---|---|---|
| Exact | 1.2.3 |
Only 1.2.3 |
Caret ^ |
^1.2.3 |
>=1.2.3 <2.0.0 (safe updates) |
Tilde ~ |
~1.2.3 |
>=1.2.3 <1.3.0 (patch only) |
| Wildcard | 1.2.x |
Any patch version |
| Wildcard | 1.x |
Any minor/patch |
| Range | >=1.0.0 <2.0.0 |
Explicit range |
| Or | 1.0.0 || 2.0.0 |
Either version |
| Latest | * |
Any version (avoid in production) |
What ^ and ~ actually do
# Package installed at version 1.2.3
"^1.2.3" # Allows 1.2.4, 1.3.0, 1.9.9 — NOT 2.0.0
"~1.2.3" # Allows 1.2.4, 1.2.9 — NOT 1.3.0
# For versions below 1.0.0, ^ is more conservative
"^0.2.3" # Allows 0.2.4, 0.2.9 — NOT 0.3.0
"^0.0.3" # Only allows 0.0.3 (exact)
# Install with exact version (no range)
npm install --save-exact react
# Saves "react": "18.2.0" instead of "react": "^18.2.0"
Dist tags
npm install react@latest # Latest stable
npm install react@next # Pre-release / next tag
npm install react@18.2.0 # Exact version
# View available tags
npm view react dist-tags
# Set a tag on your own package
npm dist-tag add my-pkg@2.0.0-beta.1 next
npm dist-tag rm my-pkg next
package-lock.json
# Lock file records exact installed versions for reproducible installs
# Always commit package-lock.json to version control
# Never manually edit it
# Regenerate lock file
rm package-lock.json && npm install
# Check if lock file is in sync with package.json
npm ci --dry-run
# View why a package is installed
npm explain lodash
npm why lodash # Alias
Listing and inspecting packages
# List all installed packages (deep tree)
npm list
# List only top-level packages
npm list --depth=0
# List global packages
npm list -g --depth=0
# Check if a specific package is installed
npm list express
# Get package info from registry
npm info express
npm info express version # Just the version
npm info express versions # All published versions
npm view express description # Specific field
# See all versions matching a range
npm view express@^4 version
.npmrc configuration
# ~/.npmrc (global user settings)
# or .npmrc in project root (project settings)
# Set custom registry
registry=https://registry.npmjs.org/
# Scoped registry (e.g. private packages)
@mycompany:registry=https://npm.mycompany.com/
# Save exact versions by default (no ^ prefix)
save-exact=true
# Set default save-prefix
save-prefix=~
# Disable running scripts on install (security)
ignore-scripts=false
# Set cache directory
cache=~/.npm
# Proxy settings
proxy=http://proxy.company.com:8080
https-proxy=http://proxy.company.com:8080
# Authentication (for private registries)
//registry.npmjs.org/:_authToken=${NPM_TOKEN}
npx — run without installing
# Run a package binary without installing globally
npx create-react-app my-app
npx tsc --version
npx eslint src/
# Run a specific version
npx jest@27 --version
# Run a local binary (from node_modules/.bin)
npx mocha tests/
# Create new project templates
npx create-next-app@latest my-app
npx create-vue my-app
# Run a Node.js script from a URL (be careful — trust the source!)
npx https://cdn.jsdelivr.net/gh/user/tool/run.js
Security and audit
# Scan for vulnerabilities
npm audit
# Get detailed JSON output
npm audit --json
# Auto-fix vulnerabilities (safe fixes only)
npm audit fix
# Fix including breaking changes (semver major)
npm audit fix --force
# Ignore specific advisory (use sparingly)
npm audit --audit-level=high # Only show high/critical
# Lock a vulnerable transitive dependency
npm install depname@safe-version
Understanding audit output
# Example:
found 2 vulnerabilities (1 moderate, 1 high)
run `npm audit fix` to fix them, or `npm audit` for details
# Severity levels:
# info < low < moderate < high < critical
npm workspaces (monorepo)
// Root package.json
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*",
"apps/*"
]
}
my-monorepo/
├── package.json (root — private: true)
├── node_modules/ (shared dependencies hoisted here)
├── packages/
│ ├── utils/
│ │ └── package.json { "name": "@myco/utils" }
│ └── ui/
│ └── package.json { "name": "@myco/ui" }
└── apps/
└── web/
└── package.json { "name": "@myco/web" }
# Install all workspace dependencies
npm install
# Run a script in a specific workspace
npm run build --workspace=packages/utils
npm run build -w packages/utils # Short form
# Run a script in all workspaces
npm run test --workspaces
npm run test -ws
# Install a package in a specific workspace
npm install lodash -w packages/utils
# Install a local workspace package in another
npm install @myco/utils -w apps/web
# Adds "@myco/utils": "*" to apps/web/package.json
# List all workspaces
npm query .workspace
Publishing a package
# Step 1: Authenticate
npm login
npm whoami # Verify you're logged in
# Step 2: Set version (edit package.json or use npm version)
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 version 1.2.3 # Set specific version
# Each npm version also creates a git commit + tag automatically
# Step 3: Preview what will be published
npm pack --dry-run # Show files that will be included
npm pack # Create a .tgz to inspect locally
# Step 4: Publish
npm publish
# Publish with a tag (not latest)
npm publish --tag next
npm publish --tag beta
# Publish a scoped package publicly
npm publish --access public
# Step 5: Deprecate an old version
npm deprecate my-pkg@1.0.0 "Use v2 instead"
.npmignore / files field
Control what gets published:
# Option 1: .npmignore (like .gitignore, for npm publish)
node_modules/
src/
tests/
*.test.ts
.env
tsconfig.json
# Option 2: "files" field in package.json (whitelist — preferred)
{
"files": ["dist/", "README.md", "LICENSE"]
}
Version management with npm
# View current version
node --version
npm --version
# Update npm itself
npm install -g npm@latest
# Use a specific Node.js version (requires nvm)
nvm use 20
nvm use --lts
# Check which npm scripts are available
npm run
# Re-run last failed install (useful after network errors)
npm install
Useful one-liners
# Remove node_modules and reinstall cleanly
rm -rf node_modules package-lock.json && npm install
# Check for packages with multiple versions (deduplication candidates)
npm dedupe --dry-run
# Find why node_modules is so large
du -sh node_modules/* | sort -hr | head -20 # macOS/Linux
npx cost-of-modules # Cross-platform
# List scripts available in the project
npm run
# Open package homepage in browser
npm home express
# Open package repository
npm repo express
# Open package bugs/issues page
npm bugs express
# Star a package (requires login)
npm star express
Common patterns
Lock down Node.js version
{
"engines": {
"node": ">=20.0.0",
"npm": ">=10.0.0"
},
"engineStrict": true
}
Ensure specific npm version in CI
# In CI script
npm install -g npm@10
# Or pin in package.json
Use exact versions for critical dependencies
{
"dependencies": {
"express": "4.18.2" // Exact, not "^4.18.2"
}
}
Or set globally:
npm config set save-exact true
Pre-commit hook with husky
npm install -D husky lint-staged
npx husky init
# .husky/pre-commit
npx lint-staged
# package.json
{
"lint-staged": {
"*.{ts,tsx}": ["eslint --fix", "prettier --write"],
"*.{json,md}": "prettier --write"
}
}
Health check script
{
"scripts": {
"check": "npm audit && npm outdated && npm run test"
}
}
Common mistakes
| Mistake | Problem | Fix |
|---|---|---|
Committing node_modules/ |
Huge repo, conflicts | Add to .gitignore |
Not committing package-lock.json |
Non-reproducible installs | Always commit the lockfile |
Using npm install in CI |
Can change lockfile | Use npm ci in CI/CD |
^ on every dependency |
Unexpected breaking updates | Use --save-exact for critical deps |
| Global installs for project tools | Not reproducible for team | Add to devDependencies instead |
npm audit fix --force blindly |
May install breaking versions | Review changes first |
Publishing without "private": true |
Accidental publish | Add "private": true to app package.json |
| Mixing npm and yarn/pnpm | Conflicting lockfiles | Stick to one package manager per project |
npm vs yarn vs pnpm
| Feature | npm | yarn | pnpm |
|---|---|---|---|
| Lockfile | package-lock.json |
yarn.lock |
pnpm-lock.yaml |
| Install command | npm install |
yarn |
pnpm install |
| Add package | npm install pkg |
yarn add pkg |
pnpm add pkg |
| Run script | npm run build |
yarn build |
pnpm build |
| Workspaces | npm workspaces |
yarn workspaces |
pnpm workspaces |
| Disk space | High (copies) | High (copies) | Low (hard links) |
| Speed | Medium | Fast | Fastest |
| Plug'n'Play | No | Yes | No |
| Strict mode | No | No | Yes (by default) |
FAQ
npm install vs npm ci — when to use each?
Use npm install in development (it updates package-lock.json and resolves new versions). Use npm ci in CI/CD and deployment (it's faster, requires the lockfile, fails if out of sync, and never writes to disk).
Why does npm install add ^ before the version?
By default npm saves a caret range ("express": "^4.18.2"), allowing patch and minor updates. To save exact versions instead, run npm install --save-exact or set save-exact=true in .npmrc.
What's the difference between dependencies and devDependencies?
dependencies are required to run the app in production. devDependencies are only needed during development (tests, build tools, linters). When someone installs your published package, they don't get your devDependencies. For apps (not libraries), the distinction mainly matters for npm ci --omit=dev in production deploys.
How do I fix EACCES permission errors with global installs?
Don't use sudo npm install -g. Instead, change the default npm prefix to a user-writable location:
mkdir -p ~/.npm-global
npm config set prefix '~/.npm-global'
export PATH=~/.npm-global/bin:$PATH # Add to ~/.bashrc or ~/.zshrc
My node_modules is huge — what can I do?
First, use npm dedupe to flatten duplicates. Check what's eating space with npx cost-of-modules. Consider switching to pnpm which uses a global content-addressable store and hard links (can save GBs on disk). Also ensure devDependencies are omitted in production with npm ci --omit=dev.
How do I use a private registry (like GitHub Packages or Artifactory)?
Add to .npmrc in the project root:
@mycompany:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
This scopes the private registry to @mycompany packages only. Everything else still resolves from the public npm registry.