NPM & Yarn: The Complete Package Management Guide for 2026

February 11, 2026 · 35 min read

Every JavaScript and Node.js project depends on packages. Whether you are building a React frontend, an Express API, or a CLI tool, your package manager is the foundation that holds everything together. It resolves dependencies, manages versions, runs scripts, and enforces security policies across your entire dependency tree.

This guide covers npm (the default Node.js package manager) and Yarn (Facebook's alternative) from first principles to advanced usage. You will learn how to initialize projects, manage dependencies, write scripts, configure workspaces, publish your own packages, audit for vulnerabilities, and choose the right package manager for your team. Every section includes real commands and configuration examples you can use immediately.

⚙ Related resources: Keep our NPM Commands Cheat Sheet open as a quick reference, and check out the Node.js Complete Guide for the runtime that powers it all.

1. Introduction to Package Management

A package manager automates the process of installing, upgrading, configuring, and removing software packages. In the JavaScript ecosystem, packages (also called modules) are reusable units of code distributed through a registry. The npm registry at registry.npmjs.org hosts over two million packages, making it the largest software registry in the world.

Without a package manager, you would need to manually download library files, track their versions, resolve conflicts between transitive dependencies, and keep everything up to date. Package managers solve all of these problems. They read a manifest file (package.json), resolve a dependency tree, download exact versions, and write a lockfile that ensures every developer and CI server uses identical dependencies.

The three major JavaScript package managers are:

All three read package.json, install to node_modules (with exceptions), and publish to the npm registry. The differences lie in performance, disk usage, lockfile format, and advanced features like Plug'n'Play and workspace management.

# Check which package manager and version you have
npm --version
yarn --version
pnpm --version

# Check your Node.js version
node --version

2. NPM Basics

npm comes pre-installed with Node.js. When you install Node.js, you get both the node runtime and the npm CLI. Here are the foundational commands every developer needs.

Initializing a Project

# Interactive initialization (prompts for each field)
npm init

# Quick initialization with defaults (skips prompts)
npm init -y

# Initialize with a specific scope
npm init --scope=@mycompany -y

The -y flag accepts all defaults, creating a package.json with sensible values based on the current directory name. You can always edit the file afterward.

Installing Packages

# Install a production dependency
npm install express
npm install lodash axios

# Shorthand
npm i express

# Install as a dev dependency (testing, building, linting)
npm install --save-dev jest eslint prettier
npm install -D jest eslint prettier

# Install a specific version
npm install react@18.3.1

# Install a version range
npm install express@^4.0.0

# Install the latest version of a major release
npm install typescript@latest

# Install globally (CLI tools)
npm install -g nodemon typescript ts-node

# Install all dependencies from package.json
npm install

# Clean install (CI/CD pipelines)
npm ci

Uninstalling Packages

# Remove a package and update package.json
npm uninstall express
npm un express
npm remove express

# Remove a dev dependency
npm uninstall --save-dev jest

# Remove a global package
npm uninstall -g nodemon

Updating Packages

# Check for outdated packages
npm outdated

# Update all packages to the latest version within semver range
npm update

# Update a specific package
npm update lodash

# Update to latest major version (beyond semver range)
npx npm-check-updates -u
npm install

# Update global packages
npm update -g

Viewing Package Information

# View package info from the registry
npm view express
npm info express

# View all available versions
npm view express versions

# View just the latest version
npm view express version

# View package dependencies
npm view express dependencies

# List installed packages
npm list
npm ls

# List top-level only (no transitive deps)
npm list --depth=0

# List globally installed packages
npm list -g --depth=0

# Find which packages depend on a given package
npm ls lodash

3. package.json — All Fields Explained

The package.json file is the manifest that defines your project. It lists metadata, dependencies, scripts, and configuration for every tool in your workflow. Here is a comprehensive example with every commonly used field:

{
  "name": "@mycompany/web-api",
  "version": "2.4.1",
  "description": "REST API for the web application",
  "main": "dist/index.js",
  "module": "dist/index.mjs",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js",
      "types": "./dist/index.d.ts"
    },
    "./utils": {
      "import": "./dist/utils.mjs",
      "require": "./dist/utils.js"
    }
  },
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ],
  "scripts": {
    "dev": "nodemon src/index.ts",
    "build": "tsc && tsc-alias",
    "start": "node dist/index.js",
    "test": "jest --coverage",
    "test:watch": "jest --watch",
    "lint": "eslint src --ext .ts",
    "lint:fix": "eslint src --ext .ts --fix",
    "format": "prettier --write \"src/**/*.ts\"",
    "typecheck": "tsc --noEmit",
    "prepare": "husky install",
    "prepublishOnly": "npm run build && npm test"
  },
  "keywords": ["api", "rest", "express", "typescript"],
  "author": "Jane Doe <jane@example.com>",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/mycompany/web-api.git"
  },
  "bugs": {
    "url": "https://github.com/mycompany/web-api/issues"
  },
  "homepage": "https://github.com/mycompany/web-api#readme",
  "engines": {
    "node": ">=20.0.0",
    "npm": ">=10.0.0"
  },
  "dependencies": {
    "express": "^4.21.0",
    "zod": "^3.23.0",
    "cors": "^2.8.5",
    "helmet": "^7.1.0",
    "winston": "^3.14.0"
  },
  "devDependencies": {
    "@types/express": "^4.17.21",
    "@types/cors": "^2.8.17",
    "typescript": "^5.5.0",
    "jest": "^29.7.0",
    "ts-jest": "^29.2.0",
    "@types/jest": "^29.5.0",
    "eslint": "^9.0.0",
    "prettier": "^3.3.0",
    "nodemon": "^3.1.0",
    "husky": "^9.1.0"
  },
  "peerDependencies": {
    "react": ">=18.0.0"
  },
  "peerDependenciesMeta": {
    "react": {
      "optional": true
    }
  },
  "overrides": {
    "semver": "^7.6.0"
  },
  "private": true
}

Key Fields Explained

dependencies vs devDependencies vs peerDependencies

dependencies are required at runtime. Express, database drivers, authentication libraries, and utility libraries like lodash belong here. When someone installs your published package, these get installed too.

devDependencies are only needed during development. Testing frameworks (Jest, Mocha), linters (ESLint), formatters (Prettier), build tools (TypeScript, webpack), and type definitions (@types/*) belong here. They are not installed when your package is consumed as a dependency.

peerDependencies declare that your package needs a specific package to be provided by the consuming project. This is common for plugins and libraries that extend a framework. For example, a React component library declares react as a peer dependency because it expects the consumer's project to provide React, rather than bundling its own copy.

# Install as a production dependency
npm install express

# Install as a dev dependency
npm install -D typescript jest

# Peer dependencies are not installed automatically (npm 7+)
# They must be present in the consuming project

4. Semantic Versioning

Semantic versioning (semver) is a versioning scheme that communicates the nature of changes in each release. The format is MAJOR.MINOR.PATCH:

Version Range Operators

{
  "dependencies": {
    "exact": "2.4.1",
    "caret": "^2.4.1",
    "tilde": "~2.4.1",
    "star": "*",
    "greater": ">=2.0.0",
    "range": ">=2.0.0 <3.0.0",
    "or": "^2.4.0 || ^3.0.0",
    "prerelease": "3.0.0-beta.1"
  }
}
Syntax Example Matches Description
^ (caret) ^1.2.3 >=1.2.3 <2.0.0 Allows minor and patch updates
~ (tilde) ~1.2.3 >=1.2.3 <1.3.0 Allows only patch updates
Exact 1.2.3 1.2.3 only No updates allowed
* (star) * Any version Matches everything (dangerous)
>= >=2.0.0 2.0.0 and above Minimum version requirement
^0.2.3 ^0.2.3 >=0.2.3 <0.3.0 When major is 0, caret pins to minor

The caret (^) is the default when you run npm install. It provides a good balance: you get bug fixes and new features automatically, but you are protected from breaking changes. Use the tilde (~) when you want only bug fixes. Use exact versions when reproducibility is more important than getting updates.

Special Behaviors for 0.x Versions

When the major version is 0 (pre-1.0 releases), semver considers the API unstable. The caret operator becomes more restrictive:

^0.2.3  →  >=0.2.3 <0.3.0   (pins to minor, not major)
^0.0.3  →  >=0.0.3 <0.0.4   (pins to exact patch)
~0.2.3  →  >=0.2.3 <0.3.0   (same as caret for 0.x)

This is why pre-1.0 packages are considered unstable and may break between minor releases.

5. NPM Scripts

NPM scripts are the standard way to run project tasks. They are defined in the scripts field of package.json and executed with npm run <name>. Some scripts have special shortcuts.

Built-in Shortcuts

# These scripts have shortcuts (no "run" needed)
npm start        # runs "start" script
npm test         # runs "test" script (also: npm t)
npm restart      # runs "restart" or stop+start
npm stop         # runs "stop" script

# All other scripts require "run"
npm run build
npm run dev
npm run lint
npm run deploy

Pre and Post Hooks

npm automatically runs scripts prefixed with pre and post before and after the main script:

{
  "scripts": {
    "prebuild": "rm -rf dist",
    "build": "tsc",
    "postbuild": "echo 'Build complete!'",

    "pretest": "npm run lint",
    "test": "jest",
    "posttest": "echo 'All tests passed!'",

    "preinstall": "node scripts/check-env.js",
    "postinstall": "husky install",

    "prepublishOnly": "npm run build && npm test"
  }
}

When you run npm run build, npm executes prebuild, then build, then postbuild in sequence. If any step fails, the remaining steps are skipped.

Common Script Patterns

{
  "scripts": {
    "dev": "nodemon --exec ts-node src/index.ts",
    "dev:debug": "nodemon --exec 'node --inspect -r ts-node/register' src/index.ts",
    "build": "tsc -p tsconfig.build.json",
    "start": "node dist/index.js",
    "start:prod": "NODE_ENV=production node dist/index.js",

    "test": "jest",
    "test:watch": "jest --watch",
    "test:coverage": "jest --coverage",
    "test:ci": "jest --ci --coverage --reporters=default --reporters=jest-junit",
    "test:e2e": "jest --config jest.e2e.config.js",

    "lint": "eslint 'src/**/*.{ts,tsx}'",
    "lint:fix": "eslint 'src/**/*.{ts,tsx}' --fix",
    "format": "prettier --write 'src/**/*.{ts,tsx,json,css}'",
    "format:check": "prettier --check 'src/**/*.{ts,tsx,json,css}'",
    "typecheck": "tsc --noEmit",

    "db:migrate": "prisma migrate deploy",
    "db:seed": "ts-node prisma/seed.ts",
    "db:reset": "prisma migrate reset",

    "docker:build": "docker build -t my-app .",
    "docker:run": "docker compose up -d",
    "docker:stop": "docker compose down",

    "clean": "rm -rf dist node_modules/.cache",
    "check": "npm run typecheck && npm run lint && npm run test"
  }
}

Passing Arguments to Scripts

# Pass arguments after --
npm test -- --watch
npm run lint -- --fix
npm run build -- --outDir=build

# Environment variables
npm run start --port=4000

# This actually sets npm_config_port, not PORT
# Use cross-env for cross-platform env vars:
# "start": "cross-env PORT=4000 node dist/index.js"

npx: Run Packages Without Installing

# Run a package binary without global install
npx create-react-app my-app
npx create-next-app@latest my-app
npx ts-node script.ts
npx eslint src/
npx prettier --write .

# Run a specific version
npx typescript@5.5.0 --version

# Run a package from a GitHub repo
npx github:user/repo

# npx first checks local node_modules/.bin,
# then checks global installs,
# then downloads and runs temporarily

6. package-lock.json and Lockfiles

A lockfile records the exact version of every package (including transitive dependencies) that was installed. It ensures that every developer, CI server, and production deployment uses identical dependency versions, regardless of when npm install is run.

Why Lockfiles Matter

Without a lockfile, npm install resolves version ranges at the time it runs. If lodash releases a new patch version between when you installed and when your teammate installs, you could end up with different versions. This creates the classic "works on my machine" problem. The lockfile eliminates this by recording exact resolved versions.

# package-lock.json is auto-generated by npm install
# It should ALWAYS be committed to version control

# To install using exact lockfile versions:
npm ci

# To update the lockfile after changing package.json:
npm install

# To see what would change without actually installing:
npm install --dry-run

Lockfile Comparison

Package Manager Lockfile Format
npm package-lock.json JSON
Yarn Classic yarn.lock Custom (human-readable)
Yarn Berry yarn.lock YAML
pnpm pnpm-lock.yaml YAML

npm ci vs npm install

# npm install:
# - Reads package.json
# - Resolves version ranges
# - May update package-lock.json
# - Skips packages already in node_modules
# Best for: development, adding new packages

# npm ci:
# - Reads package-lock.json (fails if missing or out of sync)
# - Deletes existing node_modules entirely
# - Installs exact versions from lockfile
# - Never modifies package-lock.json
# Best for: CI/CD, production builds, fresh installs

# In CI pipelines, ALWAYS use npm ci
npm ci

Lockfile Best Practices

7. Yarn Classic vs Yarn Berry (v2+)

Yarn has two major versions with fundamentally different architectures. Understanding the distinction is important when choosing which to use.

Yarn Classic (v1)

Yarn Classic (v1.x) was released by Facebook in 2016 to address npm's shortcomings at the time: slow installs, non-deterministic dependency resolution, and no lockfile (npm added package-lock.json later). Key features:

# Install Yarn Classic globally
npm install -g yarn

# Check version (should show 1.x)
yarn --version

# Yarn Classic is in maintenance mode
# No new features, only critical fixes

Yarn Berry (v2, v3, v4)

Yarn Berry is a complete rewrite with a modular plugin architecture and radical new features. It was introduced as Yarn v2 and continues with v3 and v4. The most significant change is Plug'n'Play (PnP), which eliminates the node_modules directory entirely.

# Enable Yarn Berry in a project (uses corepack)
corepack enable
yarn init -2

# Or migrate from Classic
yarn set version berry

# Yarn Berry stores its version per-project in .yarnrc.yml
# This means different projects can use different Yarn versions

# Check version (should show 4.x)
yarn --version

Plug'n'Play Explained

# Traditional node_modules (Yarn Classic, npm):
# node_modules/
#   express/
#     node_modules/
#       body-parser/
#       ...
#   lodash/
#   ...
# Thousands of directories, slow to create, wastes disk space

# Plug'n'Play (Yarn Berry):
# .pnp.cjs          ← module resolution map
# .yarn/cache/       ← zipped packages
#   express-npm-4.21.0-abc123.zip
#   lodash-npm-4.17.21-def456.zip
# No node_modules directory at all!

PnP is faster because there is no node_modules to create, stricter because packages can only access their declared dependencies, and more space-efficient because packages are stored as compressed zip files. However, some packages and tools may not work with PnP without configuration because they hardcode node_modules paths.

# .yarnrc.yml - configure Yarn Berry

# Use PnP (default)
nodeLinker: pnp

# Or fall back to node_modules if PnP causes issues
nodeLinker: node-modules

# Or use pnpm-style linking
nodeLinker: pnpm

8. Yarn Commands

Yarn commands are similar to npm but use different syntax in some cases. Here are the essential commands for both Yarn Classic and Yarn Berry.

Installing Packages

# Install all dependencies from package.json
yarn install
yarn                    # shorthand

# Add a production dependency
yarn add express
yarn add lodash axios

# Add a dev dependency
yarn add --dev jest eslint
yarn add -D jest eslint

# Add a specific version
yarn add react@18.3.1

# Add a package globally (Classic only)
yarn global add nodemon

# Berry uses corepack instead of global add
corepack enable
# Then just use npx or project-local installs

Removing Packages

# Remove a package
yarn remove express

# Remove multiple packages
yarn remove lodash axios

Updating Packages

# Check for outdated packages
yarn outdated

# Upgrade a package within semver range
yarn upgrade lodash

# Upgrade to the latest version (beyond semver)
yarn upgrade lodash --latest

# Interactive upgrade (Classic)
yarn upgrade-interactive

# Interactive upgrade (Berry)
yarn upgrade-interactive

Useful Yarn Commands

# Run a script
yarn run build
yarn build              # shorthand (no "run" needed in Yarn)
yarn test
yarn dev

# Why is this package installed? Show the dependency chain
yarn why lodash

# List all installed packages
yarn list
yarn list --depth=0

# View package info
yarn info express
yarn info express versions

# Check for dependency issues
yarn check              # Classic only

# Clean the cache
yarn cache clean

# Link a local package for development
yarn link               # in the package directory
yarn link my-package    # in the consuming project

# Berry: use workspace protocol instead
# "my-package": "workspace:*"

Yarn Berry Specific Commands

# Deduplicate dependencies
yarn dedupe

# Run a command in a specific workspace
yarn workspace @mycompany/web-app build

# Run a command in all workspaces
yarn workspaces foreach run build

# Run commands in dependency order
yarn workspaces foreach --topological run build

# Add a plugin
yarn plugin import @yarnpkg/plugin-typescript

# List installed plugins
yarn plugin list

# Set a configuration value
yarn config set nodeLinker node-modules

# Show the current configuration
yarn config

9. npm vs yarn vs pnpm — Feature Comparison

All three package managers are production-ready and handle the fundamentals well. The differences matter for specific use cases like monorepos, disk-constrained environments, and large teams.

Feature npm Yarn Berry pnpm
Comes with Node.js Yes No (corepack) No (corepack)
Lockfile package-lock.json yarn.lock (YAML) pnpm-lock.yaml
Workspaces Yes (npm 7+) Yes (advanced) Yes
Plug'n'Play No Yes (default) No
Zero-installs No Yes No
Content-addressable store No No Yes
Strict dependency isolation No (hoisting) Yes (PnP) Yes (symlinks)
Disk efficiency Low Medium (zips) High (hard links)
Install speed Moderate Fast (PnP) Fast
Plugin system No Yes No
Constraints / policies No Yes No
Patching packages No (use patch-package) Built-in (yarn patch) Built-in (pnpm patch)
Overrides / resolutions overrides resolutions overrides
CI command npm ci yarn install --immutable pnpm install --frozen-lockfile

Command Comparison

Action npm Yarn pnpm
Install all npm install yarn pnpm install
Add package npm install pkg yarn add pkg pnpm add pkg
Add dev dep npm install -D pkg yarn add -D pkg pnpm add -D pkg
Remove package npm uninstall pkg yarn remove pkg pnpm remove pkg
Update package npm update pkg yarn up pkg pnpm update pkg
Run script npm run dev yarn dev pnpm dev
Execute binary npx pkg yarn dlx pkg pnpm dlx pkg
Clean install npm ci yarn install --immutable pnpm install --frozen-lockfile
Why installed npm ls pkg yarn why pkg pnpm why pkg

When to Choose Each

10. Workspaces and Monorepos

A monorepo is a single repository containing multiple packages or applications. Workspaces are the built-in feature that all three package managers provide for managing monorepos. They share dependencies, link local packages, and run scripts across packages.

npm Workspaces

// Root package.json
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}
my-monorepo/
  package.json
  package-lock.json
  packages/
    shared-utils/
      package.json
      src/
    ui-components/
      package.json
      src/
  apps/
    web-app/
      package.json
      src/
    api-server/
      package.json
      src/
# Install dependencies for all workspaces
npm install

# Run a script in a specific workspace
npm run build -w packages/shared-utils
npm run build --workspace=packages/shared-utils

# Run a script in multiple workspaces
npm run build -w packages/shared-utils -w packages/ui-components

# Run a script in ALL workspaces
npm run build --workspaces
npm run build -ws

# Run a script only in workspaces that have it
npm run build --workspaces --if-present

# Add a dependency to a specific workspace
npm install express -w apps/api-server

# Add a local workspace as a dependency
npm install @mycompany/shared-utils -w apps/web-app
# This creates: "@mycompany/shared-utils": "^1.0.0" in web-app's package.json

Yarn Workspaces

// Root package.json
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": [
    "packages/*",
    "apps/*"
  ]
}
# Install all workspace dependencies
yarn install

# Run a script in a specific workspace
yarn workspace @mycompany/web-app build

# Run a script in all workspaces (Berry)
yarn workspaces foreach run build

# Run in dependency order (Berry)
yarn workspaces foreach --topological run build

# Run only in changed workspaces since main branch (Berry)
yarn workspaces foreach --since=main run build

# Add a dependency to a specific workspace
yarn workspace @mycompany/api-server add express

# Reference a local workspace package (Berry)
# Use the workspace: protocol in package.json:
# "@mycompany/shared-utils": "workspace:^"

pnpm Workspaces

# pnpm-workspace.yaml (at the root)
packages:
  - "packages/*"
  - "apps/*"
# Install all workspace dependencies
pnpm install

# Run a script in a specific workspace
pnpm --filter @mycompany/web-app build

# Run in all workspaces
pnpm -r run build
pnpm --recursive run build

# Run only in changed packages
pnpm --filter "...[main]" run build

# Add a dependency to a workspace
pnpm --filter @mycompany/api-server add express

# Add a local workspace dependency
pnpm --filter @mycompany/web-app add @mycompany/shared-utils

Monorepo Best Practices

11. Publishing Packages

Publishing to the npm registry makes your package available to the entire JavaScript ecosystem. Here is the complete workflow from preparation to release.

Preparing to Publish

{
  "name": "my-awesome-lib",
  "version": "1.0.0",
  "description": "A useful library that does something awesome",
  "main": "dist/index.js",
  "module": "dist/index.mjs",
  "types": "dist/index.d.ts",
  "exports": {
    ".": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.js",
      "types": "./dist/index.d.ts"
    }
  },
  "files": [
    "dist",
    "README.md",
    "LICENSE"
  ],
  "keywords": ["utility", "helper", "awesome"],
  "author": "Your Name <you@example.com>",
  "license": "MIT",
  "repository": {
    "type": "git",
    "url": "https://github.com/you/my-awesome-lib.git"
  },
  "scripts": {
    "build": "tsup src/index.ts --format cjs,esm --dts",
    "prepublishOnly": "npm run build"
  }
}

Publishing Commands

# Create an npm account (first time only)
npm adduser

# Login (if not already logged in)
npm login

# Verify you are logged in
npm whoami

# Preview what files will be published
npm pack --dry-run

# Publish the package
npm publish

# Publish a scoped package (public by default for unscoped)
npm publish --access public

# Publish a pre-release version
npm version prerelease --preid=beta
npm publish --tag beta

# Consumers install pre-release with:
# npm install my-awesome-lib@beta

# Bump the version
npm version patch   # 1.0.0 → 1.0.1
npm version minor   # 1.0.1 → 1.1.0
npm version major   # 1.1.0 → 2.0.0

# Deprecate a version (warns users on install)
npm deprecate my-awesome-lib@1.0.0 "Critical bug, please upgrade to 1.0.1"

# Unpublish (within 72 hours, strict conditions)
npm unpublish my-awesome-lib@1.0.0

Scoped Packages

# Scoped packages are namespaced under @scope/
# They are useful for organizations or personal packages

# Create a scoped package
npm init --scope=@mycompany

# Publish a scoped package as public
npm publish --access public

# Scoped packages are private by default on npm
# To make them public, use --access public

# Install a scoped package
npm install @mycompany/my-lib

# Use in code
import { helper } from '@mycompany/my-lib';

.npmignore and files

# .npmignore - exclude files from the published package
# Similar to .gitignore but for npm publish

src/
tests/
__tests__/
*.test.ts
*.spec.ts
.eslintrc
.prettierrc
tsconfig.json
jest.config.js
.github/
.env
.env.local
node_modules/
coverage/

The files field in package.json is a whitelist approach (preferred over .npmignore). If you specify "files": ["dist"], only the dist directory plus package.json, README.md, LICENSE, and CHANGELOG.md are included.

12. Security

The JavaScript ecosystem's vast dependency tree creates a significant attack surface. A single compromised package can affect thousands of downstream projects. Security must be an active practice, not an afterthought.

npm audit

# Run a security audit
npm audit

# See detailed information
npm audit --json

# Fix vulnerabilities automatically (within semver range)
npm audit fix

# Fix with major version bumps (may break things)
npm audit fix --force

# Only audit production dependencies
npm audit --omit=dev

# Audit with Yarn
yarn audit

# Audit with pnpm
pnpm audit

Understanding Audit Output

# Example npm audit output:
# ┌───────────────┬────────────────────────────────────────────────┐
# │ Severity      │ high                                           │
# ├───────────────┼────────────────────────────────────────────────┤
# │ Package       │ minimist                                       │
# ├───────────────┼────────────────────────────────────────────────┤
# │ Dependency of │ mkdirp                                         │
# ├───────────────┼────────────────────────────────────────────────┤
# │ Path          │ mkdirp > minimist                              │
# ├───────────────┼────────────────────────────────────────────────┤
# │ Fix           │ npm audit fix                                  │
# └───────────────┘

# Severity levels: info, low, moderate, high, critical

# See which of your direct dependencies pulls in the vulnerable package
npm ls minimist

Supply Chain Safety

// Force a specific version of a transitive dependency
// package.json
{
  "overrides": {
    "minimist": "^1.2.8",
    "semver": "^7.6.0"
  }
}

// Yarn Berry uses "resolutions" instead:
{
  "resolutions": {
    "minimist": "^1.2.8"
  }
}

npm Provenance

# Publish with provenance (requires CI environment like GitHub Actions)
npm publish --provenance

# This creates a verifiable link between the package and the
# source repository + CI build that produced it

# Consumers can verify provenance on the npm website
# or with: npm audit signatures

Socket.dev and Snyk

Beyond npm audit, consider tools like Socket.dev (detects suspicious package behavior like network calls in install scripts) and Snyk (continuous vulnerability monitoring with deeper analysis). Both integrate into CI pipelines and provide early warning about supply chain attacks that npm audit might miss.

13. Private Registries and Scoped Packages

Organizations often need private packages that should not be published to the public npm registry. There are several ways to host private packages.

.npmrc Configuration

# .npmrc - npm configuration file
# Can be per-project, per-user (~/.npmrc), or global

# Set a custom registry for all packages
registry=https://registry.npmjs.org/

# Set a registry for a specific scope
@mycompany:registry=https://npm.mycompany.com/

# Authentication token for a private registry
//npm.mycompany.com/:_authToken=${NPM_TOKEN}

# Always use exact versions (no ^ or ~)
save-exact=true

# Set the default license for npm init
init-license=MIT

# Set the default author
init-author-name=Jane Doe

# Disable package-lock.json (not recommended)
# package-lock=false

# Use legacy peer deps resolution
legacy-peer-deps=true

# Engine strict mode (fail if engines requirement not met)
engine-strict=true

Private Registry Options

Solution Type Best For
npm Teams / Organizations Hosted (npm) Small teams that want managed hosting
GitHub Packages Hosted (GitHub) Teams already on GitHub
GitLab Package Registry Hosted (GitLab) Teams already on GitLab
Verdaccio Self-hosted Full control, caching proxy
Artifactory / Nexus Self-hosted Enterprise with multiple package formats
AWS CodeArtifact Cloud (AWS) Teams on AWS infrastructure

GitHub Packages Example

# .npmrc for consuming from GitHub Packages
@mycompany:registry=https://npm.pkg.github.com/
//npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}
# Publish to GitHub Packages
# 1. Set "name": "@mycompany/my-package" in package.json
# 2. Set "repository": "https://github.com/mycompany/my-package"
# 3. Authenticate:
npm login --scope=@mycompany --registry=https://npm.pkg.github.com

# 4. Publish:
npm publish

Verdaccio (Self-Hosted)

# Install Verdaccio
npm install -g verdaccio

# Start the server
verdaccio
# Runs on http://localhost:4873

# Publish to Verdaccio
npm publish --registry http://localhost:4873

# Install from Verdaccio (falls back to npm for public packages)
npm install my-private-pkg --registry http://localhost:4873
# Verdaccio config.yaml
storage: ./storage
auth:
  htpasswd:
    file: ./htpasswd
uplinks:
  npmjs:
    url: https://registry.npmjs.org/
packages:
  "@mycompany/*":
    access: $authenticated
    publish: $authenticated
  "**":
    access: $all
    proxy: npmjs

14. Performance Tips

Slow installs waste developer time and slow down CI pipelines. Here are proven techniques to speed up package management.

Caching

# npm stores packages in a global cache
npm cache ls
npm cache verify

# View cache location
npm config get cache
# Usually: ~/.npm

# Force clear cache (rarely needed)
npm cache clean --force

# Yarn Berry caches in the project (.yarn/cache)
# This enables zero-installs when committed to git

# pnpm uses a global content-addressable store
pnpm store path
# Usually: ~/.local/share/pnpm/store

CI Pipeline Optimization

# GitHub Actions - cache node_modules
name: CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: "npm"    # Caches ~/.npm automatically

      - run: npm ci       # Use ci, not install
      - run: npm test
      - run: npm run build
# pnpm CI example with caching
name: CI
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: pnpm/action-setup@v4
        with:
          version: 9
      - uses: actions/setup-node@v4
        with:
          node-version: 22
          cache: "pnpm"

      - run: pnpm install --frozen-lockfile
      - run: pnpm test
      - run: pnpm build

--prefer-offline and --offline

# Use cached packages when available, only fetch missing ones
npm install --prefer-offline

# Fail if any package is not in the cache (no network)
npm install --offline

# Yarn Berry in PnP mode with zero-installs:
# yarn install is instant because everything is in .yarn/cache

Reducing Install Size

# Install only production dependencies
npm install --omit=dev
npm ci --omit=dev

# Yarn
yarn install --production

# pnpm
pnpm install --prod

# See the actual size of node_modules
du -sh node_modules

# List the largest packages
npx cost-of-modules

# Analyze your bundle to find heavy dependencies
npx source-map-explorer dist/main.js

Performance Comparison Tips

15. Common Issues and Troubleshooting

EACCES Permission Errors

# Problem: Permission denied when installing globally
# Error: EACCES: permission denied, access '/usr/lib/node_modules'

# Solution 1: Change npm's default directory
mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
# Add to ~/.bashrc or ~/.zshrc:
# export PATH=~/.npm-global/bin:$PATH

# Solution 2: Use a Node version manager (recommended)
# nvm, fnm, or volta manage Node.js and global packages per-user
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.0/install.sh | bash
nvm install 22
nvm use 22

# NEVER use sudo with npm install -g

Peer Dependency Conflicts

# Problem: ERESOLVE unable to resolve dependency tree
# npm 7+ is stricter about peer dependencies

# Solution 1: Use --legacy-peer-deps
npm install --legacy-peer-deps

# Solution 2: Use --force (less safe)
npm install --force

# Solution 3: Fix the conflict by aligning versions
# Check what the conflict is:
npm ls react
# Then update the conflicting package or your react version

# Solution 4: Set in .npmrc to persist the workaround
# legacy-peer-deps=true

Corrupted node_modules

# When things are broken, start fresh

# Delete node_modules and lockfile, reinstall
rm -rf node_modules
rm package-lock.json    # or yarn.lock
npm install

# Or just delete node_modules and use the lockfile
rm -rf node_modules
npm ci

# Clear the npm cache if issues persist
npm cache clean --force
npm ci

Version Conflicts and Deduplication

# See duplicate packages in the tree
npm ls lodash

# Deduplicate (reorganize to reduce duplicates)
npm dedupe

# Yarn Berry
yarn dedupe

# pnpm handles this automatically via its store

"Module Not Found" Errors

# Common causes and fixes:

# 1. Package not installed
npm install missing-package

# 2. Case sensitivity (macOS is case-insensitive, Linux is not)
# import from 'MyPackage' vs 'mypackage'

# 3. Missing @types package for TypeScript
npm install -D @types/express

# 4. Phantom dependency (using a package you didn't declare)
# This works with npm's flat node_modules but fails with pnpm or PnP
# Fix: add the package to your package.json

# 5. Wrong main/exports field in the package
npm view problematic-package main exports

Lock File Merge Conflicts

# package-lock.json merge conflicts are common in teams

# The safest fix:
# 1. Accept either version of the lockfile
git checkout --theirs package-lock.json
# or
git checkout --ours package-lock.json

# 2. Regenerate it
npm install

# 3. Stage and continue the merge
git add package-lock.json
git merge --continue

Debugging npm Scripts

# See exactly what npm is doing
npm run build --loglevel verbose

# See all environment variables available in scripts
npm run env

# List all available scripts
npm run

# Check which binary would be run
npx which eslint
command -v eslint

Network Issues

# Set a proxy
npm config set proxy http://proxy.company.com:8080
npm config set https-proxy http://proxy.company.com:8080

# Increase timeout for slow networks
npm config set fetch-timeout 60000

# Use a mirror registry (China)
npm config set registry https://registry.npmmirror.com/

# Reset to default registry
npm config set registry https://registry.npmjs.org/

# Check current config
npm config list

Frequently Asked Questions

What is the difference between npm install and npm ci?

npm install reads package.json, resolves dependency versions, updates package-lock.json if needed, and installs packages into node_modules. It is designed for development and may modify your lockfile. npm ci (clean install) deletes node_modules entirely, then installs exact versions from package-lock.json without modifying it. If package-lock.json is missing or out of sync with package.json, npm ci will fail rather than silently updating. This makes npm ci faster and more reliable for CI/CD pipelines, automated builds, and production deployments where reproducibility is critical.

Should I use npm or Yarn in 2026?

Both npm and Yarn are mature, production-ready package managers in 2026. npm ships with Node.js so there is nothing extra to install, and it has closed the feature gap with Yarn over the years by adding workspaces, lockfile support, and improved performance. Yarn Berry (v2+) offers innovative features like Plug'n'Play (zero node_modules), offline caching, and constraints for enforcing policies across monorepos. pnpm is a third option that uses hard links for extreme disk efficiency and strict dependency isolation. Choose npm for simplicity and zero setup, Yarn Berry for advanced monorepo features and PnP, or pnpm for disk efficiency and strictness. All three handle dependencies reliably.

What does the caret (^) and tilde (~) mean in package.json versions?

The caret (^) and tilde (~) are semantic versioning range operators in package.json. The caret (^1.2.3) allows updates that do not modify the left-most non-zero digit, so ^1.2.3 matches >=1.2.3 and <2.0.0, allowing minor and patch updates. The tilde (~1.2.3) allows only patch-level updates, so ~1.2.3 matches >=1.2.3 and <1.3.0. The caret is the default when you run npm install, and it provides a good balance between getting bug fixes and avoiding breaking changes. Use exact versions (no prefix) for maximum reproducibility, or use the tilde when you want only bug fixes without new features.

How do I fix npm vulnerabilities found by npm audit?

Run npm audit to see a report of known vulnerabilities in your dependency tree. For automatic fixes, run npm audit fix, which updates vulnerable packages to the latest patched version within your semver range. If a fix requires a major version bump, use npm audit fix --force, but test thoroughly because it may introduce breaking changes. For vulnerabilities in nested dependencies you cannot directly control, check if the parent package has released an update. You can also use npm ls <vulnerable-package> to see which dependencies pull it in. If no fix is available, assess the actual risk: a vulnerability in a dev-only dependency used at build time may not be exploitable in production. Use overrides in package.json to force a specific version of a transitive dependency when needed.

What are workspaces and how do I set up a monorepo with npm or Yarn?

Workspaces let you manage multiple packages within a single repository (monorepo). In npm, add a workspaces field to your root package.json pointing to package directories, such as ["packages/*"]. Running npm install at the root will install dependencies for all workspaces, hoisting shared dependencies to the root node_modules. You can run scripts in specific workspaces with npm run build -w packages/my-lib, or across all workspaces with npm run build --workspaces. Yarn workspaces work similarly but also support the foreach command to run scripts across all packages. Yarn Berry adds workspace constraints for enforcing policies. For large monorepos, tools like Turborepo or Nx add task caching and dependency-aware task scheduling on top of workspaces.

Related Resources

Related Resources

Node.js Complete Guide
Master Node.js from fundamentals to production deployment
TypeScript Complete Guide
Add type safety to your JavaScript and npm packages
NPM Commands Cheat Sheet
Quick reference for every npm command you need
Docker Complete Guide
Containerize your Node.js apps with optimized npm installs
Git Complete Guide
Version control for every project that uses npm
Bash Scripting Guide
Automate npm workflows with shell scripts