Mastering workspaces in package.json: A Simple Path to Scalable Monorepos

Mastering workspaces in package.json: A Simple Path to Scalable Monorepos
Photo by Rene Baker / Unsplash

As your JavaScript or TypeScript projects grow, maintaining them as a collection of separate Git repositories can become chaotic. That’s where monorepos and workspaces step in to make life easier.

Whether you're managing a design system, an API, a CLI, or even multiple microservices, the workspaces feature in package.json allows you to organize all related packages within a single repository. This article breaks down what workspaces are, why they matter, how they work, and what you need to be aware of when using them.


What Are Workspaces?

A workspace is essentially a standalone npm package located in a subdirectory of your project. With the workspaces field in your root package.json, you tell npm or Yarn to treat these subdirectories as first-class packages, which can be linked locally, share dependencies, and be managed together.

{
  "private": true,
  "workspaces": ["packages/*"]
}

This tells the package manager to treat every folder inside packages/ as a workspace.

Important: Your root package.json must have "private": true to enable workspaces.

Why Use Workspaces?

Here’s why many developers adopt workspaces in monorepos:

  • Local package linking: No need to publish internal packages just to use them — they're linked instantly.
  • Single dependency tree: Shared dependencies like React or TypeScript are deduplicated.
  • Centralized commands: You can install, test, lint, or build across all packages from one place.
  • Atomic installs: A single npm install (or yarn install) sets everything up.
  • Cross-package development: Changes in one package can instantly affect another that depends on it — no npm publish, no npm install ../relative/path.

A Simple Example Structure

my-monorepo/
├── package.json         ← Root config
├── packages/
│   ├── core-lib/
│   │   └── package.json
│   ├── api-server/
│   │   └── package.json
│   └── frontend-app/
│       └── package.json

Each of these subdirectories is its own npm package — fully versioned and self-contained — but aware of each other thanks to workspaces.


How They Interact

Suppose you have @my/core-lib being used by both @my/api-server and @my/frontend-app.

core-lib/package.json:

{
  "name": "@my/core-lib",
  "version": "1.0.0",
  "main": "index.js"
}

api-server/package.json:

{
  "name": "@my/api-server",
  "dependencies": {
    "@my/core-lib": "^1.0.0"
  }
}

Now, when you run npm install from the root, npm links @my/core-lib locally — it won’t fetch it from the npm registry. This makes development blazingly fast, and version bumps can be managed manually or with release tools like Changesets.


Commands at Scale

With workspaces, you can:

npm install         # Installs everything in all workspaces
npm run build -w @my/core-lib      # Run build only in core-lib
npm run test --workspaces          # Run test in all workspaces

You can also use powerful tools like Lerna or TurboRepo on top for task orchestration.


When Should You Use Workspaces?

Use workspaces if you:

  • Maintain multiple packages that need to be developed and versioned together.
  • Have a backend, frontend, and shared logic that are logically separated but live in one repo.
  • Want better control of internal packages without publishing to npm.
  • Prefer a modular project structure that supports reuse and clean boundaries.

Things to Watch Out For

  • Dependency conflicts: If multiple workspaces use different versions of a package, resolving the tree can get messy.
  • Overhead: For small projects, workspaces may be overkill.
  • Tooling compatibility: Not all older tools or CI systems support workspaces smoothly.
  • Deployment: You'll need to carefully decide how to build and deploy each workspace independently or as a group.

Bonus Tips

  • You can define custom folder names like "apps/*" or "modules/*" instead of packages/*.
  • npm@9+ supports npm workspaces foreach to run commands across workspaces with better control.
  • Combine with TypeScript’s project references to build TypeScript workspaces incrementally.

Finally

Workspaces are a powerful tool to help scale JavaScript and TypeScript codebases. They promote modularity, reuse, and developer productivity. While they might not be necessary for every project, they’re essential for monorepos and large-scale applications with multiple interdependent packages.

If you're still building projects by copying shared logic between folders or pushing internal libraries to npm just for reuse, it's time to look into workspaces — they’ll simplify your workflow more than you expect.

Support Us