Skip to content

Node.js npm Workspaces — Complete Guide to Monorepo Management

DodaTech Updated 2026-06-28 4 min read

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

How do npm workspaces compare to yarn workspaces?

Both are similar. npm workspaces were inspired by yarn. Syntax and behavior are largely compatible.

Can I use workspaces with TypeScript project references?

Yes. TypeScript project references work well with workspaces for separate tsconfig per package.

How do I add a dependency to only one workspace?

Use npm install -w . The dependency is only added to that workspace.

What happens if two workspaces need different versions of the same package?

npm installs both versions. One in root node_modules, another in the workspace node_modules.

How do I publish all workspaces?

Use npm publish --workspaces. Publishes each workspace with a version bump.

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