Node.js npm Workspaces — Complete Guide to Monorepo Management
In this tutorial, you will learn about Node.js npm Workspaces. We cover key concepts, practical examples, and best practices to help you master this topic.
npm workspaces enable managing multiple packages within a single Repository, sharing dependencies through hoisting, and running scripts across all packages with a single command.
What You'll Learn
By the end of this tutorial, you'll configure npm workspaces for monorepos, share dependencies between packages, run workspace scripts, manage inter-package dependencies, and avoid common workspace pitfalls.
Why Workspaces Matter
Monorepos simplify dependency management, enable atomic commits across packages, share build tooling, and reduce CI time. Workspaces provide native npm support without external tools like Lerna.
Real-World Use
A SaaS platform uses workspaces with packages for shared-utils, api-client, web-app, admin-dashboard, and worker-service, all sharing TypeScript config and ESLint rules from a root package.
Workspaces Path
flowchart LR
A[npm Publishing] --> B[npm Workspaces]
B --> C[Dependency Management]
C --> D[Testing]
D --> E[CI/CD]
B --> F{You Are Here}
style F fill:#f90,color:#fff
Configuring Workspaces
Define workspace paths in root package.json. Each workspace has its own package.json.
// Root package.json
{
"name": "my-monorepo",
"private": true,
"workspaces": [
"packages/*",
"apps/*",
"shared/*"
]
}
// packages/utils/package.json
{ "name": "@myapp/utils", "version": "1.0.0" }
// apps/web/package.json
{ "name": "@myapp/web", "version": "1.0.0", "dependencies": { "@myapp/utils": "*" } }
Installing Dependencies
npm install in the root installs all workspace dependencies with hoisting.
# Install all workspace dependencies
npm install
# Add dependency to a specific workspace
npm install lodash -w packages/utils
# Add dev dependency to root
npm install typescript -D -w
# Install across all workspaces
npm install -ws
Running Scripts
Execute scripts in specific workspaces or across all workspaces.
# Run test in all workspaces
npm run test -ws
# Run build in specific workspace
npm run build -w packages/utils
# Run script in multiple workspaces
npm run lint -w packages/utils -w apps/web
# Run scripts in workspace order (dependencies first)
npm run build -ws --if-present
Inter-Package Dependencies
Workspaces resolve local packages as symlinks. Changes are instantly available without publishing.
// apps/web/package.json
{
"dependencies": {
"@myapp/utils": "workspace:*",
"@myapp/api-client": "workspace:^1.0.0"
}
}
// workspace:* matches any version locally
// workspace:^1.0.0 uses semver but resolves locally
Hoisting and Node_modules Structure
npm hoists shared dependencies to the root node_modules. Different versions are kept in workspace node_modules.
const fs = require("node:fs");
const path = require("node:path");
function analyzeHoisting(root) {
const rootModules = path.join(root, "node_modules");
const hoisted = fs.readdirSync(rootModules).filter((d) => !d.startsWith("."));
console.log("Hoisted packages:", hoisted.length);
const workspaces = fs.readdirSync(path.join(root, "packages"));
workspaces.forEach((ws) => {
const wsModules = path.join(root, "packages", ws, "node_modules");
if (fs.existsSync(wsModules)) {
console.log(`Workspace ${ws} has its own:`, fs.readdirSync(wsModules));
}
});
}
Common Mistakes
1. Mixed Package Managers
npm workspaces are not compatible with yarn or pnpm lockfiles. Stick to one package manager.
2. Circular Dependencies Between Workspaces
Package A depends on B and B depends on A. This causes resolution failures. Restructure to avoid cycles.
3. Ignoring Hoisting Conflicts
Two workspaces requiring different major versions of the same package may cause unexpected behavior.
4. Running Scripts Without Dependency Order
Build scripts must run in topological order. npm handles this with -ws flag automatically.
5. Publishing Individual Workspace Packages
Each workspace with a public package.json can be published independently. Ensure version syncing.
Practice Questions
1. How do you define workspaces in package.json?
Add a "workspaces" array with glob patterns for workspace directories.
2. How does npm resolve inter-workspace dependencies?
It creates symlinks in node_modules pointing to the local workspace directory.
3. What does the -ws flag do?
It runs the command across all workspaces, respecting dependency order.
4. What is dependency hoisting?
Moving shared dependencies to the root node_modules to avoid duplication across workspaces.
5. Challenge: Create a Monorepo with two packages where one depends on the other.
mkdir -p packages/{utils,app}
echo '{"name":"@demo/utils","version":"1.0.0","main":"index.js"}' > packages/utils/package.json
echo '{"name":"@demo/app","version":"1.0.0","dependencies":{"@demo/utils":"*"}}' > packages/app/package.json
echo '{"private":true,"workspaces":["packages/*"]}' > package.json
FAQ
Mini Project: Monorepo Script Runner
Build a script that shows workspace dependency order and runs builds.
const { execSync } = require("node:child_process");
const path = require("node:path");
const root = process.cwd();
const pkg = require(path.join(root, "package.json"));
console.log("Workspaces:", pkg.workspaces);
console.log("Building in dependency order...");
execSync("npm run build --workspaces --if-present", { stdio: "inherit", cwd: root });
console.log("All workspaces built successfully");
What's Next
Node.js Dependency Management Node.js Testing Node.js CI/CD
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro