Running Development Mode in Multiple NPM Workspaces

When working on a monorepo with multiple packages, getting everything up and running efficiently can be a challenge. I’ve been there—jumping between terminal windows, manually starting different services, and hoping I didn’t forget anything. Fortunately, NPM workspaces provide a great way to manage multiple packages in a single repository, and with the right setup, starting all of them in development mode can be effortless.

Thank me by sharing on Twitter 🙏

I’ll walk through a few approaches to achieve this, ranging from built-in NPM commands to using turbo, a high-performance task runner that makes managing workspaces even easier.

Setting Up a Dev Script with NPM Workspaces

NPM has built-in support for workspaces, which means we can run commands across multiple packages from the root of the project. To start, I make sure my package.json at the root level includes the following:

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

This tells NPM that all projects inside the packages/ folder should be treated as part of the workspace.

Next, I add a dev script to my root package.json:

JSON
{
  "scripts": {
    "dev": "npm run dev --workspaces --if-present"
  }
}

This command runs dev in all workspace packages that define the script. The --if-present flag ensures that it won’t fail if some packages don’t have a dev script.

Each package inside packages/* should have its own dev script, like so:

JSON
{
  "scripts": {
    "dev": "next dev"
  }
}

Now, when I run:

Plaintext
npm run dev

it starts the dev script in all workspaces that have one. This is simple and effective but doesn’t allow much flexibility if I need to control execution order or handle dependencies between packages.

Running Scripts in Parallel with npm-run-all

While the built-in NPM approach is useful, sometimes I want more control. That’s where npm-run-all comes in. First, I install it as a development dependency:

JSON
npm install npm-run-all --save-dev

Then, I modify my root package.json:

JSON
{
  "scripts": {
    "dev": "npm-run-all --parallel dev:package1 dev:package2"
  }
}

Since npm-run-all doesn’t work with --workspaces, I create scripts that explicitly run dev for each package:

JSON
{
  "scripts": {
    "dev:package1": "npm --workspace=packages/package1 run dev",
    "dev:package2": "npm --workspace=packages/package2 run dev"
  }
}

Now, running npm run dev starts both package1 and package2 in parallel. This approach works well if I need to start only specific workspaces instead of all of them at once.

Using turbo for Efficient Execution

For larger projects, I prefer using turbo, which speeds up execution by caching tasks and running only what’s necessary. First, I install turbo:

Plaintext
npm install turbo --save-dev

Then, I update my root package.json:

JSON
{
  "scripts": {
    "dev": "turbo run dev"
  }
}

To configure turbo, I create a turbo.json file in the root of the project:

JSON
{
  "$schema": "https://turbo.build/schema.json",
  "ui": "tui",
  "tasks": {
    "build": {
      "inputs": ["$TURBO_DEFAULT$", ".env*"],
      "dependsOn": ["^build"],
      "outputs": ["build/**", ".vercel/**", "dist/**", ".next/**", "!.next/cache/**"]
    },
    "test": {
      "outputs": ["coverage/**"],
      "dependsOn": []
    },
    "lint": {
      "dependsOn": ["^build", "^lint"]
    },
    "check-types": {
      "dependsOn": ["^build", "^check-types"]
    },
    "dev": {
      "dependsOn": ["^dev"],
      "cache": false,
      "persistent": true
    }
  }
}

This setup ensures that turbo runs dev scripts efficiently across all workspaces. Running npm run dev now starts everything while respecting dependencies and avoiding unnecessary work.

Choosing the Right Approach

Each of these methods works well depending on the needs of the project:

  • Built-in NPM Workspaces – Great for simple monorepos where all packages need to start at once.
  • npm-run-all – Useful when needing to control which workspaces run in parallel.
  • turbo – Best for performance optimization, ensuring only necessary tasks run.

By using the right tool for the job, I can make working with multiple packages in a monorepo much smoother. No more juggling terminal windows—just a single command to kick everything off.

Share this:

Leave a Reply