Skip to content
Build With SyntaxKit

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 read

On 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:16

Then 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:

schema.prismaGenerator and datasource config; everything else loads from siblings
auth.generated.prismaAuto-generated by pnpm auth:generate; do not hand-edit
billing.prismaSubscription, StripeWebhookEvent
ai.prismaChat, Message, AiUsageEvent
effects.prismaOutboundEffect for side-effect idempotency
migrations/Versioned SQL migrations applied by Prisma
seed.tsMulti-mode seed runner: bootstrap, demo, test

What's Modeled

How The Models Connect

The diagram below shows the foreign-key relationships across the four domains.

Entity relationship diagram of the SyntaxKit data model

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:dev

Prisma prompts for a name; pick something descriptive (add_team_invitations, not update).

Apply pending migrations

On a fresh checkout, run:

pnpm db:migrate:dev

This 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:reset

Or wipe and load demo data in one shot:

pnpm db:reset:demo
Inspect data

Open the Prisma Studio GUI:

pnpm db:studio
Regenerate the typed client
pnpm db:generate

pnpm 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:generate

Never 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:deploy

Two important details:

  • The migrator service in docker-compose.yml runs this command for self-hosted Docker deploys. Run it before bringing up web after every schema change.
  • pnpm db:migrate:status reports 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:

ModeCommandPurpose
bootstrappnpm db:seed:bootstrap or pnpm db:seedDefault. Leaves the database clean so first sign-up creates the personal organization.
demopnpm db:seed:demoSample organizations and an admin (admin@demo.syntaxkit.com / password123).
testpnpm db:seed:testFixtures 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

Also useful: AI, Deployment, Going To Production, and Conventions.

Was this page helpful?

On this page