Database
PostgreSQL via Prisma in packages/database. Point DATABASE_URL at a hosted provider or Docker; the client picks the driver adapter. Schema layout, migrations, seeding, and how the models connect.
Last updated on
5 min readOn This Page
Pick a database
For the first-run path (URL → env → migrate), use Quickstart. This page covers adapters, schema layout, and day-to-day workflows.
Create a Postgres database on Neon, Supabase, AWS RDS, or any other Postgres provider, then paste the connection string into DATABASE_URL:
DATABASE_URL="postgresql://<user>:<password>@<host>/<database>"The client picks a driver from DATABASE_URL: @prisma/adapter-neon when the host ends with .neon.tech (and is not a local denylist host such as localhost), otherwise @prisma/adapter-pg. Under NODE_ENV=test / Vitest it always uses pg. Application code stays the same either way.
For local development, the simplest path is a one-off Postgres container:
docker run --name syntaxkit-postgres \
-e POSTGRES_USER=postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=syntaxkit \
-p 5432:5432 \
-d postgres:16Then point DATABASE_URL at it:
DATABASE_URL="postgresql://postgres:postgres@localhost:5432/syntaxkit"For production self-hosting, the docker-compose.yml at the repo root does not bundle a Postgres service. Provide a DATABASE_URL to a managed instance (Neon, Supabase, RDS, ...) or a separate self-hosted Postgres, then run the bundled migrator service to apply schema migrations before bringing up web.
Schema Layout
The Prisma schema is split across several files in packages/database/prisma/ so each domain has a clear home:
What's Modeled
How The Models Connect
The diagram below shows the foreign-key relationships across the four domains.
Left off for readability: standalone tables (Verification, StripeWebhookEvent, OutboundEffect) that are keyed by name rather than relation, plus creator / inviter / active-org edges used for attribution and session context.
Product data (Subscription, Chat, AiUsageEvent, and messages via their chat) is org-scoped. Membership (Member) is the access path into an organization. Creator and active-org foreign keys on users and sessions exist for attribution and session context; they are not a second tenancy model.
Common Workflows
Expand the task you're doing.
Add or change a model
Edit the right schema in packages/database/prisma/models/, then create a migration:
pnpm db:migrate:devPrisma prompts for a name; pick something descriptive (add_team_invitations, not update).
Apply pending migrations
On a fresh checkout, run:
pnpm db:migrate:devThis applies every migration in prisma/migrations/ and regenerates the typed client. The :dev suffix matches prisma migrate dev and is intended for development only; production deploys use pnpm db:migrate:deploy (see Migrations In Production).
Reset and reseed locally
Wipe the local database and start clean:
pnpm db:resetOr wipe and load demo data in one shot:
pnpm db:reset:demoInspect data
Open the Prisma Studio GUI:
pnpm db:studioRegenerate the typed client
pnpm db:generatepnpm install does not run prisma generate. After a fresh clone or a hand-edit to any *.prisma file, run pnpm db:generate (or let dev / build pull it in via Turbo's ^db:generate dependency).
Regenerate Better Auth-owned models
pnpm auth:generateNever edit auth.generated.prisma by hand. Update packages/auth/src/server.ts instead and rerun this script.
Migrations In Production
Production deploys use the non-interactive migrator:
pnpm db:migrate:deployTwo important details:
- The
migratorservice indocker-compose.ymlruns this command for self-hosted Docker deploys. Run it before bringing upwebafter every schema change. pnpm db:migrate:statusreports whether the database is in sync with the migrations directory; useful in CI and on deploy targets that do not auto-migrate.
For platform-specific runbooks (Vercel, Fly.io, Render, Docker), see Deployment and Going To Production.
Seeding
prisma db seed runs through packages/database/prisma/seed.ts, which dispatches to one of three modes:
| Mode | Command | Purpose |
|---|---|---|
| bootstrap | pnpm db:seed:bootstrap or pnpm db:seed | Default. Leaves the database clean so first sign-up creates the personal organization. |
| demo | pnpm db:seed:demo | Sample organizations and an admin (admin@demo.syntaxkit.com / password123). |
| test | pnpm db:seed:test | Fixtures used by pnpm test:integration and e2e (CI). |
Using The Client In Your Code
Import the singleton client from @syntaxkit/database and write Prisma queries the usual way:
import { prisma } from "@syntaxkit/database";
const orgs = await prisma.organization.findMany({
where: { members: { some: { userId } } },
});prisma is a process-wide singleton. The driver adapter is chosen from DATABASE_URL (see Pick a database), so application code never has to know which Postgres it is talking to.
Where To Go Next
Authentication
Better Auth wiring, sessions, and the models in auth.generated.prisma.
Billing
How Subscription mirrors Stripe and how webhooks stay idempotent.
Also useful: AI, Deployment, Going To Production, and Conventions.
Conventions
How to name files, flow types from a Zod schema to a React component, write tests, and extend the starter. Quick reference above the subsystem guides.
API
One oRPC router in packages/api, mounted at /rpc. Same router and Zod schemas feed the admin-only OpenAPI reference at /api-reference. Middleware, namespaces, React callers, streaming chat, and how to add a procedure.
