Every JavaScript project needs a package manager. npm ships with Node.js and is the default. pnpm offers better disk efficiency and speed. Bun bundles a package manager with its runtime. Each has trade-offs for install speed, disk usage, monorepo support, and ecosystem compatibility.
Quick comparison
| Feature | npm | pnpm | Bun |
|---|---|---|---|
| Install speed | Slowest | Fast | Fastest |
| Disk usage | High | Low (hard links) | Medium |
| Monorepo support | Workspaces | First-class | Workspaces |
| Lockfile | package-lock.json | pnpm-lock.yaml | bun.lockb (binary) |
| Strictness | Loose | Strict | Loose |
| Runtime | Node.js | Node.js | Bun |
npm
npm is the default package manager for Node.js. Every Node installation includes it.
Strengths
- Zero setup: Already installed with Node
- Universal support: Every tutorial assumes npm
- Mature: Stable, well-documented, predictable
Usage
# Install dependencies
npm install
# Add package
npm install lodash
# Add dev dependency
npm install -D typescript
# Run script
npm run build
# Update packages
npm update
Workspaces
npm 7+ supports workspaces for monorepos:
{
"workspaces": ["packages/*", "apps/*"]
}
# Install all workspaces
npm install
# Run script in specific workspace
npm run build -w packages/ui
Limitations
- Flat node_modules with hoisting can hide dependency issues
- Slower than pnpm and Bun for large projects
- Higher disk usage (no deduplication across projects)
pnpm
pnpm (performant npm) uses a content-addressable store and hard links.
How it works
Instead of copying packages to each project's node_modules, pnpm:
- Downloads packages to a global store (
~/.pnpm-store) - Creates hard links from projects to the store
- Each package version exists once on disk
~/.pnpm-store/
├── lodash@4.17.21/
├── react@18.2.0/
└── typescript@5.0.0/
project-a/node_modules/
└── .pnpm/
└── lodash@4.17.21 → ~/.pnpm-store/lodash@4.17.21
project-b/node_modules/
└── .pnpm/
└── lodash@4.17.21 → ~/.pnpm-store/lodash@4.17.21
Strengths
- Disk efficiency: Packages stored once, linked everywhere
- Strict dependencies: Only declared dependencies are accessible
- Fast: Hard links are instant, no copying
- Monorepo-first: Excellent workspace support
Installation
# Via npm
npm install -g pnpm
# Via Corepack (Node.js 16.9+)
corepack enable pnpm
# Via Homebrew
brew install pnpm
Usage
Commands mirror npm:
pnpm install
pnpm add lodash
pnpm add -D typescript
pnpm run build
pnpm update
Workspaces
Create pnpm-workspace.yaml:
packages:
- 'packages/*'
- 'apps/*'
# Run in specific package
pnpm --filter ui build
# Run in all packages
pnpm -r build
# Add dependency to specific package
pnpm --filter web add react
Strict mode
pnpm enforces declared dependencies. You can't import packages you didn't declare:
// If "lodash" is not in package.json, this fails
import _ from 'lodash';
// Error: Cannot find module 'lodash'
This catches phantom dependencies that would break in other environments.
Bun
Bun is a JavaScript runtime that includes a package manager, bundler, and test runner.
Strengths
- Extremely fast: Native implementation, no Node.js overhead
- Drop-in replacement: npm-compatible commands
- Binary lockfile: Faster parsing than JSON/YAML
- Integrated tooling: Runtime + bundler + test runner
Installation
# macOS/Linux
curl -fsSL https://bun.sh/install | bash
# Windows (via scoop)
scoop install bun
Usage
bun install
bun add lodash
bun add -d typescript
bun run build
bun update
Speed
Bun's package manager is written in Zig and optimized for speed:
| Operation | npm | pnpm | Bun |
|---|---|---|---|
| Clean install | ~30s | ~15s | ~5s |
| Cached install | ~10s | ~3s | ~1s |
| Add package | ~5s | ~2s | ~0.5s |
Times vary by project size and network.
Binary lockfile
Bun uses bun.lockb, a binary format:
# Generate lockfile
bun install
# View as text
bun bun.lockb
Binary lockfiles are faster to parse but not human-readable.
Limitations
- Different runtime (some Node APIs differ)
- Smaller ecosystem than Node.js
- Native modules may need recompilation
- Binary lockfile harder to review in PRs
Choosing a package manager
Use npm when
- You want zero setup and maximum compatibility
- Your CI/documentation assumes npm
- You're working on a simple project
- Team familiarity is paramount
Use pnpm when
- You manage multiple projects sharing dependencies
- Disk space matters (CI caches, local development)
- You use monorepos (pnpm has excellent support)
- You want strict dependency enforcement
Use Bun when
- Install speed is critical
- You're willing to use the Bun runtime
- You want integrated tooling (runtime + bundler + tests)
- You're starting a new project
Mixing package managers
Avoid mixing package managers in a project. Lockfiles are incompatible:
| Manager | Lockfile |
|---|---|
| npm | package-lock.json |
| pnpm | pnpm-lock.yaml |
| Bun | bun.lockb |
Enforce with packageManager field:
{
"packageManager": "pnpm@8.15.0"
}
Corepack respects this and uses the specified manager.
Monorepo considerations
| Feature | npm | pnpm | Bun |
|---|---|---|---|
| Workspace linking | ✅ | ✅ | ✅ |
| Filtering | -w flag | --filter | --filter |
| Hoisting control | Limited | Full | Limited |
| Turborepo integration | ✅ | ✅ (recommended) | ✅ |
pnpm is often recommended for monorepos due to its strict dependency resolution and excellent filtering.
CI caching
Cache strategies differ by manager:
# npm
- uses: actions/cache@v4
with:
path: ~/.npm
key: npm-${{ hashFiles('package-lock.json') }}
# pnpm
- uses: actions/cache@v4
with:
path: ~/.pnpm-store
key: pnpm-${{ hashFiles('pnpm-lock.yaml') }}
# Bun
- uses: actions/cache@v4
with:
path: ~/.bun/install/cache
key: bun-${{ hashFiles('bun.lockb') }}
Migration path
npm to pnpm
# Delete node_modules and lockfile
rm -rf node_modules package-lock.json
# Install with pnpm
pnpm install
Most projects migrate seamlessly. Fix any phantom dependency errors by adding missing packages to package.json.
npm to Bun
# Delete node_modules and lockfile
rm -rf node_modules package-lock.json
# Install with Bun
bun install
Test thoroughly—Bun's runtime may behave differently for some Node APIs.
Summary
npm is the safe default with universal compatibility. pnpm offers better speed and disk efficiency with strict dependency management—ideal for monorepos. Bun provides the fastest installs and integrated tooling but requires adopting its runtime. Choose based on your project's needs: compatibility (npm), efficiency (pnpm), or speed (Bun). Commit one lockfile and enforce a single manager per project.
