|
| 1 | +--- |
| 2 | +title: Coordinating development runtime dependencies |
| 3 | +description: Learn how to coordinate development services that depend on each other at runtime. |
| 4 | +product: turborepo |
| 5 | +type: guide |
| 6 | +summary: Combine a readiness probe with with and dependsOn to coordinate runtime dependencies between development services. |
| 7 | +prerequisites: |
| 8 | + - /docs/crafting-your-repository/developing-applications |
| 9 | +related: |
| 10 | + - /docs/reference/configuration |
| 11 | + - /docs/reference/run |
| 12 | +--- |
| 13 | + |
| 14 | +Some development tasks need another service to be available before they can start. For example, a frontend may need an API to be accepting requests before its development server starts. |
| 15 | + |
| 16 | +<Callout type="info"> |
| 17 | + This pattern is intended for coordinating services during local development. For CI and production, use the service orchestration and readiness capabilities provided by your CI provider or deployment platform. |
| 18 | +</Callout> |
| 19 | + |
| 20 | +Turborepo can coordinate this workflow by combining: |
| 21 | + |
| 22 | +- [`with`](/docs/reference/configuration#with) to start the service alongside the task that needs it |
| 23 | +- [`dependsOn`](/docs/reference/configuration#dependson) to wait for a finite readiness probe to succeed |
| 24 | +- [`persistent`](/docs/reference/configuration#persistent) to mark the service as long-running |
| 25 | + |
| 26 | +This guide configures packages named `web` and `api`, where the API's development server exposes a health endpoint at `http://localhost:3001/health`. |
| 27 | + |
| 28 | +## Create a readiness probe |
| 29 | + |
| 30 | +The readiness probe should retry until the service is available, exit successfully when it is ready, and fail after a timeout. Keep the readiness condition specific to your service so it can check everything the dependent task requires, such as a database connection or completed initialization. |
| 31 | + |
| 32 | +```js title="./apps/api/scripts/wait-for-ready.mjs" |
| 33 | +const healthUrl = "http://localhost:3001/health"; |
| 34 | +const deadline = Date.now() + 60_000; |
| 35 | + |
| 36 | +while (Date.now() < deadline) { |
| 37 | + try { |
| 38 | + const response = await fetch(healthUrl, { |
| 39 | + signal: AbortSignal.timeout(1_000), |
| 40 | + }); |
| 41 | + |
| 42 | + if (response.ok) { |
| 43 | + console.log(`API is ready at ${healthUrl}`); |
| 44 | + process.exit(0); |
| 45 | + } |
| 46 | + } catch { |
| 47 | + // The service may still be starting. Try again until the deadline. |
| 48 | + } |
| 49 | + |
| 50 | + await new Promise((resolve) => setTimeout(resolve, 500)); |
| 51 | +} |
| 52 | + |
| 53 | +console.error(`API did not become ready at ${healthUrl} within 60 seconds`); |
| 54 | +process.exit(1); |
| 55 | +``` |
| 56 | + |
| 57 | +Add scripts for the long-running service and its finite readiness probe: |
| 58 | + |
| 59 | +```json title="./apps/api/package.json" |
| 60 | +{ |
| 61 | + "name": "api", |
| 62 | + "scripts": { |
| 63 | + "dev": "node ./server.mjs", |
| 64 | + "dev:ready": "node ./scripts/wait-for-ready.mjs" |
| 65 | + } |
| 66 | +} |
| 67 | +``` |
| 68 | + |
| 69 | +The `api#dev:ready` script does not start the API. It only reports whether the API started by `api#dev` is ready. |
| 70 | + |
| 71 | +The `web` application also needs a `dev` script: |
| 72 | + |
| 73 | +```json title="./apps/web/package.json" |
| 74 | +{ |
| 75 | + "name": "web", |
| 76 | + "scripts": { |
| 77 | + "dev": "start-web" |
| 78 | + } |
| 79 | +} |
| 80 | +``` |
| 81 | + |
| 82 | +## Register the tasks |
| 83 | + |
| 84 | +Register both task names in the root `turbo.json`. The readiness probe must not be cached because its result depends on the state of a running service rather than files in the repository. |
| 85 | + |
| 86 | +```json title="./turbo.json" |
| 87 | +{ |
| 88 | + "$schema": "https://turborepo.dev/schema.json", |
| 89 | + "tasks": { |
| 90 | + "dev": { |
| 91 | + "cache": false, |
| 92 | + "persistent": true |
| 93 | + }, |
| 94 | + "dev:ready": { |
| 95 | + "cache": false |
| 96 | + } |
| 97 | + } |
| 98 | +} |
| 99 | +``` |
| 100 | + |
| 101 | +Next, use a [Package Configuration](/docs/reference/package-configurations) to describe the `web` application's runtime requirements: |
| 102 | + |
| 103 | +```json title="./apps/web/turbo.json" |
| 104 | +{ |
| 105 | + "extends": ["//"], |
| 106 | + "tasks": { |
| 107 | + "dev": { |
| 108 | + "with": ["api#dev"], |
| 109 | + "dependsOn": ["api#dev:ready"] |
| 110 | + } |
| 111 | + } |
| 112 | +} |
| 113 | +``` |
| 114 | + |
| 115 | +When `web#dev` is selected, Turborepo: |
| 116 | + |
| 117 | +1. Adds the persistent `api#dev` task to the run because of `with`. |
| 118 | +2. Starts `api#dev` and the non-persistent `api#dev:ready` probe. |
| 119 | +3. Starts `web#dev` after the probe exits successfully. |
| 120 | +4. Continues monitoring `api#dev` and `web#dev` until you stop Turborepo or either task exits. |
| 121 | + |
| 122 | +Do not add `api#dev` to `dependsOn`. A persistent task does not exit, so it cannot complete and unblock a dependent task. The finite `api#dev:ready` probe provides that completion signal instead. |
| 123 | + |
| 124 | +## Run the application |
| 125 | + |
| 126 | +Select the `web` application to start the complete workflow: |
| 127 | + |
| 128 | +```bash title="Terminal" |
| 129 | +turbo run dev --filter=web |
| 130 | +``` |
| 131 | + |
| 132 | +The default concurrency has enough capacity for this workflow. If you set [`--concurrency`](/docs/reference/run#--concurrency-number--percentage), use at least `3` so Turborepo has a slot for each persistent task and another for the readiness probe. |
| 133 | + |
| 134 | +If the API does not become ready before the probe's timeout, the probe exits with an error and `web#dev` does not start. Turborepo stops all tasks when you interrupt the run. |
| 135 | + |
| 136 | +## Probe other readiness conditions |
| 137 | + |
| 138 | +An HTTP endpoint is only one way to determine readiness. The probe script can wait for any condition your service exposes, including: |
| 139 | + |
| 140 | +- A TCP port accepting connections |
| 141 | +- A file or Unix socket being created |
| 142 | +- A database accepting queries |
| 143 | +- A specific initialization check succeeding |
| 144 | + |
| 145 | +You can implement the probe yourself, as shown above, or use a utility such as [`wait-on`](https://www.npmjs.com/package/wait-on). In either case, ensure the probe retries transient failures, has a timeout, and exits with a non-zero status when the service does not become ready. |
0 commit comments