apps/docs/content/docs/crafting-your-repository/developing-applications.mdx
Developing applications in a monorepo unlocks powerful workflows, enabling you to make atomic commits to source control with easy access to code.
Most development tasks are long-running tasks that watch for changes to your code. Turborepo enhances this experience with a powerful terminal UI and other capabilities like:
dev tasksDefining a development task in turbo.json tells Turborepo that you'll be running a long-lived task. This is useful for things like running a development server, running tests, or building your application.
To register a dev task, add it to your turbo.json with two properties:
{
"tasks": {
"dev": {
"cache": false,
"persistent": true
}
}
}
You can now run your dev task to start your development scripts in parallel:
turbo dev
devYou may also want to run scripts that set up your development environment or pre-build packages. You can make sure those tasks run before the dev task with dependsOn:
{
"tasks": {
"dev": {
"cache": false,
"persistent": true,
"dependsOn": ["//#dev:setup"]
},
"//#dev:setup": {
"outputs": [".codegen/**"]
}
}
}
In this example, we're using a Root Task but you can use the same idea for arbitrary tasks in packages.
The --filter flag allows you to pick a subset of your Package Graph so you can run your dev task for a specific application and its dependencies:
turbo dev --filter=web
Turborepo's terminal UI enables a number of features that create a highly interactive experience around your tasks.
You can quickly adjust the UI to your needs using keybinds.
| Keybind | Action |
|---|---|
m | Toggle popup listing keybinds |
↑/↓ | Select the next/previous task in the task list |
j/k | Select the next/previous task in the task list |
p | Toggle selection pinning for selected task |
h | Toggle visibility of the task list |
c | When logs are highlighted, copy selection to the system clipboard |
u/d | Scroll logs up and down |
Some of your tools may allow you to type input into them. Examples of this include Drizzle ORM's interactive migrations or Jest's filtering and re-running of test suites.
You can interact with tasks that are marked as interactive to give them input.
| Keybind | Action |
|---|---|
i | Begin interacting |
Ctrl+z | Stop interacting |
Many tools have a built-in watcher, like tsc --watch,
that will respond to changes in your source code. However, some don't.
turbo watch adds a dependency-aware watcher to any tool. Changes to source code will follow the Task Graph that you've described in turbo.json, just like all your other tasks.
For example, using a package structure like create-turbo with the following tasks and scripts:
<Tabs items={["turbo.json", "packages/ui", "apps/web"]}> <Tab value="turbo.json">
{
"tasks": {
"dev": {
"persistent": true,
"cache": false
},
"lint": {
"dependsOn": ["^lint"]
}
}
}
{
"name": "@repo/ui"
"scripts": {
"dev": "tsc --watch",
"lint": "eslint ."
}
}
{
"name": "web"
"scripts": {
"dev": "next dev",
"lint": "eslint ."
},
"dependencies": {
"@repo/ui": "workspace:*"
}
}
When you run turbo watch dev lint, you'll see the lint scripts are re-run whenever you make source code changes, despite ESLint not having a built-in watcher. turbo watch is also aware of internal dependencies, so a code change in @repo/ui will re-run the task in both @repo/ui and web.
The Next.js development server in web and the TypeScript Compiler's built-in watcher in @repo/ui will continue to work as usual, since they are marked with persistent.
For more information, visit the turbo watch reference.
In some cases, you may want to run a script when the dev task is stopped. Turborepo is unable to run those teardown scripts when exiting because turbo exits when your dev tasks exit.
Instead, create a turbo dev:teardown script that you run separately after you've exited your primary turbo dev task.
Once you have a version of your application that you'd like to deploy, it's time to learn how to configure environment variables in Turborepo.