| 1 | # Prisma TanStack Start |
| 2 | |
| 3 | Minimal TanStack Start app with Prisma Next queries, Prisma Postgres, and |
| 4 | Prisma Compute managed by Alchemy. |
| 5 | |
| 6 | ## What It Creates |
| 7 | |
| 8 | - A Prisma project. |
| 9 | - The project-owned default `main` branch created by Prisma. |
| 10 | - A Prisma Postgres database attached to `main` via `Prisma.Postgres(...)`. |
| 11 | - A database connection with redacted connection strings. |
| 12 | - A Prisma App. |
| 13 | - A single Deployment built from TanStack Start's `.output` artifact. |
| 14 | - Runtime env vars managed by `Prisma.Compute` in the branch environment and |
| 15 | snapshotted into each Deployment. |
| 16 | - An optional Prisma App custom domain when `PRISMA_TANSTACK_DOMAIN` is set. |
| 17 | - A Prisma Next contract in `src/prisma/contract.prisma`. |
| 18 | - Generated Prisma Next artifacts in `src/prisma/contract.json` and |
| 19 | `src/prisma/contract.d.ts`. |
| 20 | - A checked-in, versioned initial migration under `migrations/app/`. |
| 21 | - An opt-in local seed script and server routes that query through |
| 22 | `@prisma-next/postgres/runtime` without returning database IDs or user data. |
| 23 | |
| 24 | Alchemy emits the current contract, transactionally applies checked-in Prisma |
| 25 | Next migrations, and verifies the live schema as an explicit `Command.Exec` |
| 26 | release step before Compute builds the artifact: |
| 27 | |
| 28 | ```sh |
| 29 | bun run db:migrate |
| 30 | bun run build |
| 31 | ``` |
| 32 | |
| 33 | `migration apply` is idempotent and the strict emit/apply/verify sequence runs |
| 34 | on every deploy, so an unplanned contract change, database restore, or schema |
| 35 | drift (including unexpected extra schema objects) fails the release instead of |
| 36 | being hidden by local memo state. PostgreSQL lock waits are capped at 30 seconds, |
| 37 | statements at five minutes, and Alchemy terminates the entire migration process |
| 38 | group after six minutes. The direct database credential exists only in that |
| 39 | command. Artifact packaging is pure and the |
| 40 | Alchemy process's Prisma Management API credentials are excluded from both the |
| 41 | migration subprocess and the application build. The production deployment |
| 42 | receives only its pooled `DATABASE_URL`. Production deploys never seed or |
| 43 | delete application data. Plan and review versioned migrations in source |
| 44 | control, keep changes backward compatible with the currently promoted |
| 45 | Deployment, and use expand/contract changes when a schema transition spans |
| 46 | releases. The Deployment starts `server/index.mjs` on port `3000`. |
| 47 | |
| 48 | ## Durable State Prerequisite |
| 49 | |
| 50 | This stack always uses `Cloudflare.state()`. Alchemy state is stored in a |
| 51 | Cloudflare Worker backed by a Durable Object with embedded SQLite; its bearer |
| 52 | token and encryption key live in Cloudflare Secrets Store. There is |
| 53 | intentionally no local-state fallback. Alchemy does not currently ship a |
| 54 | Prisma-hosted remote state store, so this example selects the built-in durable |
| 55 | backend explicitly. |
| 56 | |
| 57 | Before the first deploy or local development session, make one Cloudflare |
| 58 | account available to every trusted operator and CI runner that will deploy or |
| 59 | destroy this stack, then bootstrap the state store once: |
| 60 | |
| 61 | ```sh |
| 62 | export CLOUDFLARE_ACCOUNT_ID="..." |
| 63 | export CLOUDFLARE_API_TOKEN="..." |
| 64 | bunx alchemy provider cloudflare bootstrap |
| 65 | ``` |
| 66 | |
| 67 | Keep those Cloudflare credentials configured for every later dev, deploy, and |
| 68 | destroy command. The default state-store Worker is shared across stacks and |
| 69 | stages; Alchemy scopes this stack's records by stack name and stage. |
| 70 | |
| 71 | ## Local Dev |
| 72 | |
| 73 | ```sh |
| 74 | export CLOUDFLARE_ACCOUNT_ID="..." |
| 75 | export CLOUDFLARE_API_TOKEN="..." |
| 76 | bun install |
| 77 | bun run dev |
| 78 | ``` |
| 79 | |
| 80 | `alchemy dev` starts a local Prisma Postgres with `@prisma/dev`, passes |
| 81 | `DATABASE_URL` and `DIRECT_URL` for local migration convenience, runs |
| 82 | `bun run db:setup`, and then starts Vite with `bun run dev:start`. Production |
| 83 | runtime keeps the narrower single-credential contract described below. |
| 84 | |
| 85 | Open `http://localhost:3000`. |
| 86 | |
| 87 | If port 3000 is busy: |
| 88 | |
| 89 | ```sh |
| 90 | PRISMA_TANSTACK_DEV_PORT=3011 bun run dev |
| 91 | ``` |
| 92 | |
| 93 | ## Prisma Next Workflow |
| 94 | |
| 95 | ```sh |
| 96 | bun run emit # regenerate contract.json + contract.d.ts |
| 97 | bunx prisma-next migration plan --name <change> # plan and review a migration |
| 98 | bun run db:migrate # emit, transactionally apply, and verify the live schema |
| 99 | bun run seed # opt-in demo data; never run by deploy |
| 100 | bun run db:setup # migrate + demo seed, used by local dev |
| 101 | ``` |
| 102 | |
| 103 | The Vite dev server also auto-emits the contract on edits. The explicit scripts |
| 104 | remain the source of truth for deploys and CI so builds are reproducible. |
| 105 | |
| 106 | The runtime uses `pg`, so the example uses `connection.databaseUrl` |
| 107 | to pass the pooled `postgres://` endpoint as `DATABASE_URL`. It explicitly |
| 108 | omits `DIRECT_URL`, duplicate pooled variables, and Prisma resource IDs from the |
| 109 | runtime environment. The schema command receives the direct endpoint only for |
| 110 | the duration of contract application. |
| 111 | |
| 112 | ## Deploy |
| 113 | |
| 114 | ```sh |
| 115 | export CLOUDFLARE_ACCOUNT_ID="..." |
| 116 | export CLOUDFLARE_API_TOKEN="..." |
| 117 | export PRISMA_SERVICE_TOKEN="..." |
| 118 | export PRISMA_TANSTACK_APP="my-team-web-production" |
| 119 | bun run deploy --stage production |
| 120 | ``` |
| 121 | |
| 122 | The App name must be stable and unique within the Prisma workspace. The example |
| 123 | defaults it from the Alchemy stage for local use; production and CI should set |
| 124 | `PRISMA_TANSTACK_APP` to a deterministic environment-specific value. The |
| 125 | Project name is provider-generated by default so interrupted creates can be |
| 126 | recovered safely; set `PRISMA_PROJECT` only when an externally fixed Console |
| 127 | name is required. |
| 128 | |
| 129 | The deploy output prints the Compute URL. Copy its `url` value before checking |
| 130 | the app: |
| 131 | |
| 132 | ```sh |
| 133 | URL="https://your-app.prisma.build" |
| 134 | ``` |
| 135 | |
| 136 | To attach a custom domain during deploy, set: |
| 137 | |
| 138 | ```sh |
| 139 | export PRISMA_TANSTACK_DOMAIN="app.example.com" |
| 140 | bun run deploy --stage production |
| 141 | ``` |
| 142 | |
| 143 | The stack output includes the custom domain status and DNS records to configure. |
| 144 | |
| 145 | ## Check It |
| 146 | |
| 147 | ```sh |
| 148 | curl --fail-with-body --show-error "$URL/" |
| 149 | curl --fail-with-body --show-error "$URL/api/health" |
| 150 | ``` |
| 151 | |
| 152 | The health response should include: |
| 153 | |
| 154 | ```json |
| 155 | { |
| 156 | "ok": true, |
| 157 | "database": "ready", |
| 158 | "configuration": "ready" |
| 159 | } |
| 160 | ``` |
| 161 | |
| 162 | ## Destroy |
| 163 | |
| 164 | ```sh |
| 165 | export CLOUDFLARE_ACCOUNT_ID="..." |
| 166 | export CLOUDFLARE_API_TOKEN="..." |
| 167 | export PRISMA_SERVICE_TOKEN="..." |
| 168 | export PRISMA_TANSTACK_APP="my-team-web-production" |
| 169 | bun run destroy --stage production |
| 170 | ``` |