Skip to main content

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?​

Featurepnpmnpmyarn
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

Resources​