DatabasePrisma Migrations

Prisma Migrations

All schema changes go through PrismaPrismaThe TypeScript ORM HiveCFM uses to talk to Postgres. The schema lives at packages/database/schema.prisma. migrations. The workflow is: edit schema.prisma, then run a command that generates SQL and applies it. Never hand-edit tables in production with ALTER TABLE — the migration file is the durable record of the change.

Where things live

hivecfm-core/packages/database/
├── schema.prisma           # Source of truth for the schema
├── migrations/             # One folder per migration, oldest first
│   ├── 20260131161101_add_whatsapp_sms_channels/
│   ├── 20260303000000_add_novu_integration_type/
│   ├── 20260304000000_campaign_novu_refactor/
│   ├── 20260307000000_add_tenant_license/
│   └── 20260405000000_add_under_review_status/
├── src/                    # Seed scripts, generators
├── dist/                   # Build output: generated client + migration runner
└── package.json            # db:migrate:dev, db:migrate:deploy, db:seed

Each migration folder contains a migration.sql file. The folder name is <timestamp>_<snake_case_description>; timestamps are generated when you run prisma migrate dev.

Adding a field — the normal workflow

Say you want to add nickname to the User model:

1. Edit schema.prisma

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  nickname  String?  // <- new field
  // …
}

2. Generate and apply the migration

cd hivecfm-core
pnpm run db:migrate

Under the hood this runs pnpm --filter @hivecfm/database db:migrate:dev, which:

  1. Builds @hivecfm/database (vite build).
  2. Runs prisma generate to regenerate the typed client.
  3. Runs the repo’s own apply-migrations.js, which diffs the schema against the database, creates a new migration folder under migrations/, and applies it.

The script will prompt for a migration name if one is not supplied via --name. Pick a short snake_case description — it becomes the folder suffix and shows up in every git log forever.

3. Commit the new migration folder

git add packages/database/schema.prisma packages/database/migrations/
git commit -m "feat(db): add User.nickname"

The schema.prisma change and the generated migrations/<timestamp>_add_user_nickname/ folder both belong in the same commit. Code review verifies the SQL matches the schema diff.

Migration workflow in production

Production does not run db:migrate:dev. It runs the deploy variant:

pnpm --filter @hivecfm/database db:migrate:deploy

Differences:

db:migrate:devdb:migrate:deploy
Generates new migrationsYes (from schema diff)No — applies only what exists in migrations/
Regenerates the clientYesNo (client ships with the built image)
InteractiveAsks for a migration nameNon-interactive
UsesDATABASE_URLMIGRATE_DATABASE_URL if set, else DATABASE_URL

MIGRATE_DATABASE_URL lets ops run migrations through a privileged user while runtime uses a less-privileged one.

Resetting local dev — nuclear option

If your local DB is wedged and you do not care about the data:

cd hivecfm-core/packages/database
pnpm dlx prisma migrate reset

This drops the database, re-applies every migration, and re-runs the seed. Never run this against a shared or production database.

The friendlier alternative for a “I just want to iterate on the schema without making a migration yet” situation:

pnpm --filter @hivecfm/database db:push

db:push does a direct ALTER TABLE that matches schema.prisma, skipping migration files entirely. Use it for spikes, never commit the change until you have generated a real migration.

Common pitfalls

  • Forgetting to rebuild after a schema change. db:migrate:dev takes care of it; if you edited the schema but used a different script, run pnpm --filter @hivecfm/database build before calling the Prisma client.
  • Editing an applied migration. Do not. Add a new migration that amends the prior one instead.
  • Shadow database errors. prisma migrate dev needs a “shadow” database to diff against. In local dev the postgres container creates it automatically; if you hit “shadow database access denied”, the configured user is missing CREATE DATABASE grant.
  • Stale client after git pull. If TypeScript suddenly complains that a field does not exist, re-run pnpm --filter @hivecfm/database db:migrate:dev — it both migrates and regenerates the client.

.NET analogy

If you have used EF Core migrations, the flow is similar but inverted:

  • EF: change C# model, run dotnet ef migrations add, EF diffs model vs snapshot and writes SQL.
  • Prisma: change schema file, run prisma migrate dev, Prisma diffs schema vs live DB and writes SQL.

There is no ModelSnapshot.cs — the migrations folder and the live schema are the full history.

Troubleshooting

See the Migration stuck runbook for the most common production failure modes.