Repo
Docs
Development Tasks

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 (opens in a new tab) 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.

{
  "pipeline": {
    "dev": {
      "cache": false,
      "persistent": true
    }
  }
}

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. We also set persistent to true, because dev tasks are long-running tasks, and we want to ensure that it doesn't block any other task from executing.

Setting up package.json

You should also provide a dev task in your root 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.

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

Then, in your app's 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

Let's assume you want to run the dev task in the docs workspace, located at <root>/apps/docs. turbo can infer the workspace from your directory, so if you run:

cd <root>/apps/docs
turbo run dev

turbo will automatically pick up that you're in the docs workspace and run the dev task.

To run the same task from any other location in the repository, use --filter syntax. For example:

turbo run dev --filter docs

Running setup tasks

There may be some tasks that you need to run before your persistent, long-running development tasks are running. Some examples of setup steps would be:

  • Pre-building a package
  • Setting up Docker containers
  • Checking Node or package manager versions

If you name these setup tasks dev, you'll have a naming collision in turbo.json with your persistent tasks. Instead, you can rename those setup tasks to setup-dev and depend on those setup-dev tasks for your dev tasks:

{
  "pipeline": {
    "dev": {
      "dependsOn": [
        // Wait for tasks in dependencies
        "^setup-dev"
        // Wait for tasks in same package
        "setup-dev"
        // Wait for `setup-dev` in a specific package
        "my-package#setup-dev"
        ],
    },
    "setup-dev": {},
  }
}

Good to know:

  • If your tasks produce cacheable artifacts, make sure to include those in the outputs key of setup-dev.
  • If your tasks do not produce cacheable artifacts, you likely want to set the cache key to false to make sure it always runs first.

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 (opens in a new tab) to solve this problem.

We want every dev to have a great experience using Turbo. The approach documented below does not live up to those standards.

We're working on a first-class solution to this problem - but while you wait, here's the next-best solution.

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:

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.