pnpm & Monorepo Workspaces
Overviewβ
The application uses pnpm 10.33.0 with workspaces to manage a monorepo containing multiple applications, libraries, and shared packages. pnpm provides fast, disk-efficient package management with built-in workspace support.
What is pnpm?β
pnpm (performant npm) is a package manager that:
- Fast: Uses content-addressable storage for superior performance
- Disk-Efficient: Hard links packages instead of duplication
- Strict: Enforces dependency isolation with hoist-only mode
- Monorepo-Ready: Native workspace support
- npm-Compatible: Understands package.json and lock files
Why pnpm Over npm/yarn?β
| Feature | pnpm | npm | yarn |
|---|---|---|---|
| Speed | β‘β‘β‘ Fast | β‘ Medium | β‘β‘ Fast |
| Disk Space | β‘β‘ Efficient | β‘ Wasteful | β‘β‘ Efficient |
| Workspaces | β Great | β οΈ Basic | β Great |
| Lock File | β Fast | β Good | β οΈ Slow |
| Strictness | β Very Strict | β οΈ Loose | β οΈ Loose |
App Workspace Structureβ
Root Configurationβ
pnpm-workspace.yamlβ
packages:
- 'apps/*'
- 'packages/*'
- 'tests/*'
# Shared dependencies
shared:
- node_modules/.pnpm
catalogs:
default:
react: 19.0.0
nestjs: 11.0.0
typescript: 5.3.0
Workspace Layoutβ
qms/
βββ apps/
β βββ api/ # NestJS backend app
β β βββ package.json
β βββ web/ # Next.js frontend app
β βββ package.json
βββ packages/
β βββ schemas/ # Shared DTO schemas
β β βββ package.json
β βββ constants/ # Shared constants
β βββ package.json
βββ tests/
β βββ api/ # API tests
β β βββ package.json
β βββ e2e/ # E2E tests
β β βββ package.json
β βββ performance/ # Performance tests
β βββ package.json
βββ pnpm-workspace.yaml
βββ pnpm-lock.yaml
βββ package.json (root)
Installation & Setupβ
Install pnpmβ
# Using npm
npm install -g pnpm@10.33.0
# Using Homebrew (macOS)
brew install pnpm
# Using Chocolatey (Windows)
choco install pnpm
# Verify installation
pnpm --version # Should output: 10.33.0
Initialize Workspaceβ
# Already initialized in the application, but for new workspaces:
pnpm init -y # Create root package.json
# Create workspace structure
mkdir -p apps/api apps/web packages/schemas packages/constants tests/{api,e2e,performance}
Root package.jsonβ
{
"name": "qms",
"version": "1.0.0",
"description": "Quality Management System for Toyota Motor Asia",
"private": true,
"engines": {
"node": ">=18.0.0",
"pnpm": "10.33.0"
},
"scripts": {
"install": "pnpm install",
"dev": "pnpm --parallel -r --filter '!@qms/tests-*' run start:dev",
"build": "pnpm -r --filter '!@qms/tests-*' run build",
"start": "pnpm -r --filter '!@qms/tests-*' run start",
"test": "pnpm -r run test",
"test:e2e": "pnpm --filter @qms/tests-e2e test",
"lint": "pnpm -r run lint",
"format": "pnpm -r run format",
"clean": "pnpm -r exec rm -rf dist coverage node_modules",
"clean:lock": "rm -f pnpm-lock.yaml && pnpm install",
"changeset": "changeset",
"version": "changeset version",
"release": "changeset publish"
},
"devDependencies": {
"@changesets/cli": "^2.27.0"
},
"pnpm": {
"overrides": {
"typescript": "5.3.0"
}
}
}
Installing Dependenciesβ
Install All Dependenciesβ
# Install dependencies for all packages
pnpm install
# Install with frozen lock file (CI environments)
pnpm install --frozen-lockfile
# Skip dev dependencies
pnpm install --prod
Add Dependenciesβ
# Add to root (development only)
pnpm add -D typescript eslint
# Add to specific package
pnpm --filter @qms/api add express
# Add to multiple packages
pnpm --filter './packages/**' add lodash
# Add peer dependency
pnpm add --save-peer react
Update & Remove Dependenciesβ
# Update all packages
pnpm update
# Update specific package
pnpm update typescript
# Update in specific workspace
pnpm --filter @qms/api update
# Remove dependency
pnpm remove lodash
# Remove from specific workspace
pnpm --filter @qms/web remove @types/react
Running Scriptsβ
Run Scripts Across Workspacesβ
# Run test in all packages
pnpm -r run test
# Run test only in specific package
pnpm --filter @qms/api run test
# Run test in multiple specific packages
pnpm --filter @qms/api --filter @qms/web run build
# Run in parallel with dependency order
pnpm -r run build
# Run only in packages that have the script
pnpm -r --parallel run start:dev
# Exclude packages
pnpm -r --filter '!@qms/tests-*' run build
Filter Patternsβ
# Filter by name
pnpm --filter @qms/api run test
# Filter by directory
pnpm --filter './apps/**' run build
# Filter by tag
pnpm --filter '...{@qms/api}' run test
# Exclude pattern
pnpm --filter '!@qms/tests-*' run build
# Dependencies of package
pnpm --filter '@qms/web...' run build
Workspace Configurationβ
Package.json Structureβ
Each workspace package should have:
{
"name": "@qms/api",
"version": "1.0.0",
"description": "QMS NestJS Backend API",
"private": true,
"type": "commonjs",
"scripts": {
"start": "node dist/main.js",
"start:dev": "nest start --watch",
"build": "nest build",
"test": "jest",
"lint": "eslint src"
},
"dependencies": {
"@nestjs/common": "^11.0.0",
"@qms/schemas": "workspace:*"
},
"devDependencies": {
"@types/node": "^20.0.0"
}
}
Workspace Protocolβ
Use workspace:* to reference local packages:
{
"dependencies": {
"@qms/schemas": "workspace:*",
"@qms/constants": "workspace:*"
}
}
This ensures:
- Local packages are symlinked
- Works during development
- Automatically resolved in published versions
Dependency Managementβ
Shared Dependenciesβ
Define once in root, shared across packages:
{
"pnpm": {
"overrides": {
"typescript": "5.3.0",
"eslint": "8.50.0"
}
}
}
Dependency Resolutionβ
# Check for duplicate dependencies
pnpm ls typescript
# Show dependency tree
pnpm ls --depth=10
# Check for outdated packages
pnpm outdated
# Check for unused dependencies
pnpm exec depcheck
Peer Dependency Handlingβ
{
"peerDependencies": {
"react": "^19.0.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
}
}
}
Performance Optimizationβ
.npmrc Configurationβ
# ~/.npmrc or .npmrc in project root
shamefully-hoist=false
strict-peer-dependencies=true
auto-install-peers=true
prefer-frozen-lockfile=true
verify-store-integrity=true
Speeding Up Installationβ
# Use offline mode (faster for CI)
pnpm install --offline
# Use specific registry
pnpm install --registry https://registry.npmjs.org
# Increase verbosity for debugging
pnpm install --verbose
# Use workspace protocol for local packages
# (already configured in QMS)
Monorepo Scriptsβ
Development Workflowβ
# Start all dev servers in parallel
pnpm dev
# Start only specific package
pnpm --filter @qms/api start:dev
# Start with file watching
pnpm --parallel -r run start:dev
Build Processβ
# Build all packages
pnpm build
# Build with dependency order
pnpm -r --filter '@qms/api...' build
# Build only changes (requires changesets)
pnpm build --changed
Testingβ
# Run all tests
pnpm test
# Run tests in specific package
pnpm --filter @qms/api test
# Run with coverage
pnpm test -- --coverage
# Run E2E tests
pnpm test:e2e
Cleaning & Maintenanceβ
Clean Workspaceβ
# Remove all node_modules
pnpm clean
# Remove build artifacts
pnpm -r exec rm -rf dist coverage
# Clear pnpm store
pnpm store prune
# Reset to clean state
rm -rf node_modules pnpm-lock.yaml
pnpm install
Lock File Managementβ
# Regenerate lock file
rm pnpm-lock.yaml
pnpm install
# Update lock file without installing
pnpm install --lockfile-only
# Validate lock file
pnpm ls
Common Commands Referenceβ
Installationβ
# Install all dependencies
pnpm install
# Add root dependency
pnpm add -D typescript
# Add to specific workspace
pnpm --filter @qms/api add express
Running Scriptsβ
# Run in all packages
pnpm -r run build
# Run in specific package
pnpm --filter @qms/api run test
# Run in parallel
pnpm --parallel -r run start:dev
Workspacesβ
# List all workspaces
pnpm ls -r --depth=0
# Filter workspaces
pnpm --filter '@qms/api' ls
# Show what changed
pnpm changeset status
Maintenanceβ
# Check for outdated packages
pnpm outdated
# Update packages
pnpm update
# Clean up
pnpm store prune
Troubleshootingβ
Dependency Not Foundβ
# Reinstall all dependencies
pnpm install --force
# Clear frozen lock file
pnpm install --no-frozen-lockfile
Module Resolution Issuesβ
# Ensure workspace protocol is used
grep "workspace:\*" package.json
# Update package-lock
pnpm install --lockfile-only
Performance Issuesβ
# Enable store integrity check
pnpm install --verify-store-integrity
# Use faster disk (avoid network drives)
pnpm store status
# Increase Node memory
NODE_OPTIONS=--max_old_space_size=4096 pnpm install
Publishing from Monorepoβ
Using Changesetsβ
# Create change description
pnpm changeset
# Bump versions
pnpm version
# Publish to npm
pnpm release
Example Publishing Workflowβ
# 1. Make changes
# 2. Create changeset
pnpm changeset
# 3. Commit and push
git add . && git commit -m "chore: add new feature"
git push
# 4. On next CI run: changesets auto-version and publish
Best Practicesβ
1. Use Workspace Protocol Consistentlyβ
// β
GOOD
{
"dependencies": {
"@qms/schemas": "workspace:*"
}
}
// β AVOID
{
"dependencies": {
"@qms/schemas": "1.0.0"
}
}
2. Define Dependencies at Correct Levelβ
// β
GOOD: Shared at root
// package.json (root)
{
"devDependencies": {
"typescript": "5.3.0"
}
}
// β AVOID: Duplicated in each package
3. Use Meaningful Package Namesβ
// β
GOOD: Clear scope
{
"name": "@qms/api"
}
// β AVOID: Too generic
{
"name": "api"
}
4. Filter Scripts Appropriatelyβ
# β
GOOD: Use filters to target packages
pnpm --filter '@qms/api' run test
# β AVOID: Running everything when not needed
pnpm -r run test