Skip to content

Migrations

ArchVault uses Drizzle ORM for database schema management. Migrations are generated from TypeScript schema definitions — you never write SQL by hand.

Schema files (TypeScript) → drizzle-kit generate → SQL migration files → drizzle-kit migrate → Database
  1. You define tables in apps/web/src/lib/schema/*.ts using Drizzle’s TypeScript API
  2. drizzle-kit generate diffs the schema against existing migrations and produces new SQL
  3. drizzle-kit migrate (dev) or migrate.mjs (production) applies pending SQL to the database
  1. Edit the schema in apps/web/src/lib/schema/:

    apps/web/src/lib/schema/example.ts
    import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
    export const example = pgTable("example", {
    id: text("id").primaryKey(),
    name: text("name").notNull(),
    createdAt: timestamp("created_at").defaultNow().notNull(),
    });
  2. Generate the migration:

    Terminal window
    pnpm db:generate

    This creates a new timestamped folder in apps/web/drizzle/ containing migration.sql.

  3. Review the generated SQL — verify it matches your intent:

    Terminal window
    cat apps/web/drizzle/$(ls -t apps/web/drizzle | head -1)/migration.sql
  4. Apply the migration:

    Terminal window
    pnpm db:migrate
  5. Commit both the schema changes and the generated migration files to git.

Uses drizzle-kit (a dev dependency):

Terminal window
# Generate migration from schema changes
pnpm db:generate
# Apply pending migrations
pnpm db:migrate

Both commands use the config in apps/web/drizzle.config.ts and read DATABASE_URL from apps/web/.env.

Migrations live in apps/web/drizzle/. Each migration is a timestamped folder:

apps/web/drizzle/
├── 20260314122457_powerful_silverclaw/
│ └── migration.sql
├── 20260314142206_bitter_thunderbolt/
│ └── migration.sql
├── ...
└── meta/
├── _journal.json ← tracks migration order and applied status
└── *.json ← snapshot of schema at each migration

The meta/_journal.json file tracks which migrations have been applied. Drizzle uses this to determine which migrations are pending.

If a migration fails partway through (e.g. a constraint violation), the database may be in a partially migrated state:

  1. Check what was applied by looking at the error output
  2. Fix the issue in your schema
  3. Generate a new corrective migration with pnpm db:generate
  4. Apply it with pnpm db:migrate

To start fresh during development:

Terminal window
# Stop and remove the database volume
pnpm docker:compose:down:v
# Restart the database
pnpm docker:compose:up
# Re-apply all migrations
pnpm db:migrate

If your database gets out of sync with the migrations (e.g. manual DDL changes), the safest fix is to reset the dev database (above). In production, always use migrations — never run DDL directly.

Ensure you’re running pnpm db:generate or pnpm db:migrate from the repo root, not from apps/web/. The Turborepo scripts handle the working directory.