Development tasks in a Monorepo

The vast majority of development workflows look like this:

  1. Open a repository
  2. Run a dev task while they develop
  3. At the end of the day, shut down the dev task and close the repository.

dev will likely be the most frequently run task in your repository, so getting it right is important.

Types of dev tasks

dev tasks come in many shapes and sizes:

  1. Running a local development server for a web app
  2. Running nodemon to re-run a backend process every time code changes
  3. Running tests in --watch mode

Setup with Turborepo

You should specify your dev task like this in your turbo.json.

turbo.json
{
  "pipeline": {
    "dev": {
      "cache": false
    }
  }
}

Since dev tasks don't produce outputs, outputs is empty. dev tasks are also unique in that you rarely want to cache them, so we set cache as false.

Setting up package.json

You should also provide a dev task in your root package.json:

package.json
{
  "scripts": {
    "dev": "turbo run dev"
  }
}

This enables developers to run the task directly from their normal task runner.

Running tasks before dev

In some workflows, you'll want to run tasks before you run your dev task. For instance, generating code or running a db:migrate task.

In these cases, use dependsOn to say that any codegen or db:migrate tasks should be run before dev is run.

turbo.json
{
  "pipeline": {
    "dev": {
      "dependsOn": ["codegen", "db:migrate"],
      "cache": false
    },
    "codegen": {
      "outputs": ["./codegen-outputs/**"]
    },
    "db:migrate": {
      "cache": false
    }
  }
}

Then, in your app's package.json:

apps/web/package.json
{
  "scripts": {
    // For example, starting the Next.js dev server
    "dev": "next",
    // For example, running a custom code generation task
    "codegen": "node ./my-codegen-script.js",
    // For example, using Prisma
    "db:migrate": "prisma db push"
  }
}

This means that users of your dev task don't need to worry about codegen or migrating their database - it gets handled for them before their development server even starts.

Running dev only in certain workspaces

To run a dev task in only certain workspaces, you should use the --filter syntax. For example:

turbo run dev --filter docs

Will only run dev in the workspace named docs.

Using environment variables

While developing, you'll often need to use environment variables. These let you customize the behavior of your program - for instance, pointing to a different DATABASE_URL in development and production.

We recommend using a library called dotenv-cli to solve this problem.

Tutorial

  1. Install dotenv-cli in your root workspace:
# Installs dotenv-cli in the root workspace
npm add dotenv-cli
  1. Add a .env file to your root workspace:
  ├── apps/
  ├── packages/
+ ├── .env
  ├── package.json
  └── turbo.json

Add any environment variables you need to inject:

.env
DATABASE_URL=my-database-url
  1. Inside your root package.json, add a dev script. Prefix it with dotenv and the -- argument separator:
{
  "scripts": {
    "dev": "dotenv -- turbo run dev"
  }
}

This will extract the environment variables from .env before running turbo run dev.

  1. Now, you can run your dev script:
npm run dev

And your environment variables will be populated! In Node.js, these are available on process.env.DATABASE_URL.

You should also add your environment variables to your turbo.json if you're using them to build your app.