1155 lines
23 KiB
Markdown
1155 lines
23 KiB
Markdown
# Database Migrations Guide
|
|
|
|
Complete guide to managing database schema changes in Changemaker Lite V2 using Prisma Migrate and Drizzle Kit.
|
|
|
|
## Overview
|
|
|
|
Changemaker Lite V2 uses **two ORMs** for different parts of the application:
|
|
|
|
- **Prisma** (Main API) - Full-featured ORM with migration tracking
|
|
- **Drizzle** (Media API) - Lightweight ORM with schema push (no migrations)
|
|
|
|
This guide covers both workflows.
|
|
|
|
## Prisma Migrations (Main API)
|
|
|
|
### Migration Workflow Overview
|
|
|
|
```
|
|
1. Edit schema.prisma
|
|
↓
|
|
2. Create migration (npx prisma migrate dev)
|
|
↓
|
|
3. Review generated SQL
|
|
↓
|
|
4. Test migration locally
|
|
↓
|
|
5. Commit migration files
|
|
↓
|
|
6. Deploy to production (npx prisma migrate deploy)
|
|
```
|
|
|
|
### Understanding Prisma Migrate
|
|
|
|
**Prisma Migrate:**
|
|
- Tracks schema changes as SQL migration files
|
|
- Stores migration history in `_prisma_migrations` table
|
|
- Ensures schema consistency across environments
|
|
- Supports rollback via version control
|
|
|
|
**Migration Files:**
|
|
- Located in `api/prisma/migrations/`
|
|
- Named with timestamp: `20260213123456_description/`
|
|
- Contains `migration.sql` (SQL commands)
|
|
|
|
**Migration States:**
|
|
- **Pending:** Not yet applied
|
|
- **Applied:** Successfully executed
|
|
- **Failed:** Execution error (requires manual fix)
|
|
|
|
### Creating Migrations
|
|
|
|
#### Step 1: Edit Prisma Schema
|
|
|
|
Edit `api/prisma/schema.prisma`:
|
|
|
|
```prisma
|
|
// Before
|
|
model User {
|
|
id Int @id @default(autoincrement())
|
|
email String @unique
|
|
password String
|
|
role Role @default(USER)
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
// After (add name field)
|
|
model User {
|
|
id Int @id @default(autoincrement())
|
|
email String @unique
|
|
password String
|
|
name String? // New field (nullable)
|
|
role Role @default(USER)
|
|
createdAt DateTime @default(now())
|
|
}
|
|
```
|
|
|
|
#### Step 2: Validate Schema
|
|
|
|
```bash
|
|
cd api
|
|
npx prisma validate
|
|
```
|
|
|
|
**Expected output:**
|
|
```
|
|
Environment variables loaded from .env
|
|
Prisma schema loaded from prisma/schema.prisma
|
|
The schema is valid ✔
|
|
```
|
|
|
|
**If errors:**
|
|
```
|
|
Error validating model "User": Field "foo" references unknown model "Bar"
|
|
```
|
|
|
|
Fix errors before proceeding.
|
|
|
|
#### Step 3: Create Migration
|
|
|
|
```bash
|
|
cd api
|
|
npx prisma migrate dev --name add_user_name
|
|
```
|
|
|
|
**What happens:**
|
|
1. Prisma detects schema changes
|
|
2. Generates SQL migration file
|
|
3. Prompts for migration name (or uses `--name` argument)
|
|
4. Applies migration to development database
|
|
5. Regenerates Prisma Client
|
|
|
|
**Expected output:**
|
|
```
|
|
Environment variables loaded from .env
|
|
Prisma schema loaded from prisma/schema.prisma
|
|
Datasource "db": PostgreSQL database "changemaker_v2_db"
|
|
|
|
Applying migration `20260213123456_add_user_name`
|
|
Running seed command `tsx prisma/seed.ts` ...
|
|
|
|
✔ Generated Prisma Client to ./node_modules/@prisma/client
|
|
```
|
|
|
|
**Migration file created:**
|
|
```
|
|
api/prisma/migrations/
|
|
└── 20260213123456_add_user_name/
|
|
└── migration.sql
|
|
```
|
|
|
|
#### Step 4: Review Generated SQL
|
|
|
|
```bash
|
|
cd api
|
|
cat prisma/migrations/20260213123456_add_user_name/migration.sql
|
|
```
|
|
|
|
**Example SQL:**
|
|
```sql
|
|
-- AlterTable
|
|
ALTER TABLE "users" ADD COLUMN "name" TEXT;
|
|
```
|
|
|
|
**Verify SQL is correct:**
|
|
- Check table names match expectations
|
|
- Ensure data types are correct
|
|
- Look for unexpected `DROP` commands
|
|
|
|
#### Step 5: Test Migration
|
|
|
|
Migration already applied to development DB. Verify:
|
|
|
|
```bash
|
|
# Check schema with Prisma Studio
|
|
cd api
|
|
npx prisma studio
|
|
```
|
|
|
|
Or query directly:
|
|
|
|
```bash
|
|
# PostgreSQL shell
|
|
docker compose exec v2-postgres psql -U changemaker_v2 -d changemaker_v2_db
|
|
|
|
# Describe users table
|
|
changemaker_v2_db=# \d users;
|
|
```
|
|
|
|
**Expected output:**
|
|
```
|
|
Column | Type | Nullable | Default
|
|
----------+---------+----------+---------
|
|
id | integer | not null | nextval(...)
|
|
email | text | not null |
|
|
password | text | not null |
|
|
name | text | | <-- New field
|
|
role | text | not null | 'USER'
|
|
created_at| timestamp| not null | now()
|
|
```
|
|
|
|
#### Step 6: Commit Migration
|
|
|
|
```bash
|
|
git add prisma/migrations/20260213123456_add_user_name/
|
|
git add prisma/schema.prisma
|
|
git commit -m "feat(db): add name field to User model"
|
|
```
|
|
|
|
**Always commit:**
|
|
- Migration directory (`prisma/migrations/*/`)
|
|
- Updated `schema.prisma`
|
|
|
|
### Applying Migrations (Production)
|
|
|
|
#### In Production Environment
|
|
|
|
```bash
|
|
cd api
|
|
npx prisma migrate deploy
|
|
```
|
|
|
|
**What it does:**
|
|
- Checks `_prisma_migrations` table for applied migrations
|
|
- Applies only **pending** migrations
|
|
- Does NOT create new migrations
|
|
- Safe for production
|
|
|
|
**Expected output:**
|
|
```
|
|
Environment variables loaded from .env
|
|
Prisma schema loaded from prisma/schema.prisma
|
|
Datasource "db": PostgreSQL database "changemaker_v2_prod_db"
|
|
|
|
2 migrations found in prisma/migrations
|
|
|
|
Applying migration `20260213123456_add_user_name`
|
|
Applying migration `20260214000000_add_user_avatar`
|
|
|
|
All migrations have been successfully applied.
|
|
```
|
|
|
|
#### In Docker
|
|
|
|
```bash
|
|
# Apply migrations in Docker container
|
|
docker compose exec api npx prisma migrate deploy
|
|
|
|
# Or during container startup (Dockerfile)
|
|
CMD npx prisma migrate deploy && npm start
|
|
```
|
|
|
|
#### CI/CD Deployment
|
|
|
|
```yaml
|
|
# GitHub Actions example
|
|
- name: Run migrations
|
|
run: |
|
|
cd api
|
|
npx prisma migrate deploy
|
|
```
|
|
|
|
### Migration Best Practices
|
|
|
|
#### 1. Incremental Changes
|
|
|
|
Make small, focused migrations:
|
|
|
|
**Good:**
|
|
```bash
|
|
# Separate migrations
|
|
npx prisma migrate dev --name add_user_name
|
|
npx prisma migrate dev --name add_user_avatar
|
|
npx prisma migrate dev --name add_user_bio
|
|
```
|
|
|
|
**Bad:**
|
|
```bash
|
|
# One huge migration
|
|
npx prisma migrate dev --name update_user_model
|
|
# (adds 10 fields, 3 relations, 5 indexes)
|
|
```
|
|
|
|
#### 2. Descriptive Names
|
|
|
|
Use clear migration names:
|
|
|
|
**Good:**
|
|
```bash
|
|
npx prisma migrate dev --name add_user_name
|
|
npx prisma migrate dev --name make_email_unique
|
|
npx prisma migrate dev --name create_posts_table
|
|
npx prisma migrate dev --name add_user_posts_relation
|
|
```
|
|
|
|
**Bad:**
|
|
```bash
|
|
npx prisma migrate dev --name update
|
|
npx prisma migrate dev --name fix
|
|
npx prisma migrate dev --name changes
|
|
```
|
|
|
|
#### 3. Review SQL Before Committing
|
|
|
|
Always review generated SQL:
|
|
|
|
```bash
|
|
cat prisma/migrations/*/migration.sql
|
|
```
|
|
|
|
**Watch for:**
|
|
- Unexpected `DROP TABLE` or `DROP COLUMN`
|
|
- Missing `NOT NULL` constraints
|
|
- Incorrect data types
|
|
- Missing indexes on foreign keys
|
|
|
|
#### 4. Backup Before Migration (Production)
|
|
|
|
```bash
|
|
# Backup database before deploy
|
|
docker compose exec v2-postgres pg_dump -U changemaker_v2 changemaker_v2_db > backup-$(date +%Y%m%d).sql
|
|
|
|
# Apply migration
|
|
npx prisma migrate deploy
|
|
|
|
# If migration fails, restore:
|
|
cat backup-20260213.sql | docker compose exec -T v2-postgres psql -U changemaker_v2 changemaker_v2_db
|
|
```
|
|
|
|
#### 5. Test on Staging First
|
|
|
|
Never deploy migrations directly to production:
|
|
|
|
```
|
|
1. Create migration in development
|
|
2. Test locally
|
|
3. Commit to version control
|
|
4. Deploy to staging environment
|
|
5. Test on staging
|
|
6. Deploy to production
|
|
```
|
|
|
|
### Common Migration Scenarios
|
|
|
|
#### Add New Field
|
|
|
|
```prisma
|
|
// schema.prisma
|
|
model User {
|
|
id Int @id @default(autoincrement())
|
|
email String @unique
|
|
name String? // New nullable field
|
|
}
|
|
```
|
|
|
|
```bash
|
|
npx prisma migrate dev --name add_user_name
|
|
```
|
|
|
|
**Generated SQL:**
|
|
```sql
|
|
ALTER TABLE "users" ADD COLUMN "name" TEXT;
|
|
```
|
|
|
|
#### Add Required Field (with Default)
|
|
|
|
```prisma
|
|
model User {
|
|
id Int @id @default(autoincrement())
|
|
email String @unique
|
|
createdAt DateTime @default(now()) // New required field with default
|
|
}
|
|
```
|
|
|
|
```bash
|
|
npx prisma migrate dev --name add_created_at
|
|
```
|
|
|
|
**Generated SQL:**
|
|
```sql
|
|
ALTER TABLE "users" ADD COLUMN "created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP;
|
|
```
|
|
|
|
#### Add New Table
|
|
|
|
```prisma
|
|
model Post {
|
|
id Int @id @default(autoincrement())
|
|
title String
|
|
content String?
|
|
published Boolean @default(false)
|
|
authorId Int
|
|
author User @relation(fields: [authorId], references: [id])
|
|
createdAt DateTime @default(now())
|
|
}
|
|
|
|
model User {
|
|
id Int @id @default(autoincrement())
|
|
email String @unique
|
|
posts Post[]
|
|
}
|
|
```
|
|
|
|
```bash
|
|
npx prisma migrate dev --name create_posts_table
|
|
```
|
|
|
|
**Generated SQL:**
|
|
```sql
|
|
CREATE TABLE "posts" (
|
|
"id" SERIAL NOT NULL,
|
|
"title" TEXT NOT NULL,
|
|
"content" TEXT,
|
|
"published" BOOLEAN NOT NULL DEFAULT false,
|
|
"author_id" INTEGER NOT NULL,
|
|
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
|
|
CONSTRAINT "posts_pkey" PRIMARY KEY ("id")
|
|
);
|
|
|
|
CREATE INDEX "posts_author_id_idx" ON "posts"("author_id");
|
|
|
|
ALTER TABLE "posts" ADD CONSTRAINT "posts_author_id_fkey"
|
|
FOREIGN KEY ("author_id") REFERENCES "users"("id")
|
|
ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
```
|
|
|
|
#### Add Relation
|
|
|
|
```prisma
|
|
model Campaign {
|
|
id Int @id @default(autoincrement())
|
|
title String
|
|
createdByUserId Int // New foreign key
|
|
createdBy User @relation(fields: [createdByUserId], references: [id])
|
|
}
|
|
|
|
model User {
|
|
id Int @id @default(autoincrement())
|
|
email String @unique
|
|
campaigns Campaign[]
|
|
}
|
|
```
|
|
|
|
```bash
|
|
npx prisma migrate dev --name add_campaign_user_relation
|
|
```
|
|
|
|
**Generated SQL:**
|
|
```sql
|
|
ALTER TABLE "campaigns" ADD COLUMN "created_by_user_id" INTEGER NOT NULL;
|
|
|
|
CREATE INDEX "campaigns_created_by_user_id_idx" ON "campaigns"("created_by_user_id");
|
|
|
|
ALTER TABLE "campaigns" ADD CONSTRAINT "campaigns_created_by_user_id_fkey"
|
|
FOREIGN KEY ("created_by_user_id") REFERENCES "users"("id")
|
|
ON DELETE RESTRICT ON UPDATE CASCADE;
|
|
```
|
|
|
|
#### Change Field Type
|
|
|
|
```prisma
|
|
// Before
|
|
model User {
|
|
age Int
|
|
}
|
|
|
|
// After
|
|
model User {
|
|
age String // Changed from Int to String
|
|
}
|
|
```
|
|
|
|
```bash
|
|
npx prisma migrate dev --name change_user_age_to_string
|
|
```
|
|
|
|
**Generated SQL:**
|
|
```sql
|
|
ALTER TABLE "users" ALTER COLUMN "age" SET DATA TYPE TEXT;
|
|
```
|
|
|
|
**Warning:** This may fail if data cannot be cast. Consider data migration first.
|
|
|
|
#### Add Unique Constraint
|
|
|
|
```prisma
|
|
model User {
|
|
email String @unique // Add unique constraint
|
|
}
|
|
```
|
|
|
|
```bash
|
|
npx prisma migrate dev --name make_email_unique
|
|
```
|
|
|
|
**Generated SQL:**
|
|
```sql
|
|
CREATE UNIQUE INDEX "users_email_key" ON "users"("email");
|
|
```
|
|
|
|
#### Add Index
|
|
|
|
```prisma
|
|
model User {
|
|
email String
|
|
|
|
@@index([email]) // Add index
|
|
}
|
|
```
|
|
|
|
```bash
|
|
npx prisma migrate dev --name add_email_index
|
|
```
|
|
|
|
**Generated SQL:**
|
|
```sql
|
|
CREATE INDEX "users_email_idx" ON "users"("email");
|
|
```
|
|
|
|
### Migration History and Status
|
|
|
|
#### Check Migration Status
|
|
|
|
```bash
|
|
cd api
|
|
npx prisma migrate status
|
|
```
|
|
|
|
**Expected output:**
|
|
```
|
|
Environment variables loaded from .env
|
|
Prisma schema loaded from prisma/schema.prisma
|
|
Datasource "db": PostgreSQL database "changemaker_v2_db"
|
|
|
|
Database schema is up to date!
|
|
|
|
Following migrations have been applied:
|
|
|
|
20260101000000_init
|
|
20260105000000_add_campaigns
|
|
20260110000000_add_locations
|
|
20260213123456_add_user_name
|
|
```
|
|
|
|
#### View Migration History
|
|
|
|
```bash
|
|
# List migration files
|
|
ls -la api/prisma/migrations/
|
|
|
|
# View specific migration
|
|
cat api/prisma/migrations/20260213123456_add_user_name/migration.sql
|
|
```
|
|
|
|
#### Check Database Migration Table
|
|
|
|
```bash
|
|
docker compose exec v2-postgres psql -U changemaker_v2 -d changemaker_v2_db -c "SELECT * FROM _prisma_migrations;"
|
|
```
|
|
|
|
**Output:**
|
|
```
|
|
id | checksum | finished_at | migration_name | logs
|
|
---+----------+-------------+----------------+-----
|
|
1 | abc123 | 2026-01-01 | 20260101000000_init | NULL
|
|
2 | def456 | 2026-01-05 | 20260105000000_add_campaigns | NULL
|
|
```
|
|
|
|
### Rollback Strategies
|
|
|
|
Prisma Migrate does **NOT** have automatic rollback. Use these strategies:
|
|
|
|
#### 1. Version Control Rollback
|
|
|
|
```bash
|
|
# Revert schema changes
|
|
git revert <commit-hash>
|
|
|
|
# Create new migration to undo changes
|
|
npx prisma migrate dev --name revert_user_name
|
|
|
|
# This creates a new migration that undoes the previous one
|
|
```
|
|
|
|
#### 2. Manual Rollback Migration
|
|
|
|
Create a new migration to reverse changes:
|
|
|
|
```prisma
|
|
// If you added a field, remove it
|
|
model User {
|
|
id Int @id @default(autoincrement())
|
|
email String @unique
|
|
// name String? // Remove this
|
|
}
|
|
```
|
|
|
|
```bash
|
|
npx prisma migrate dev --name remove_user_name
|
|
```
|
|
|
|
**Generated SQL:**
|
|
```sql
|
|
ALTER TABLE "users" DROP COLUMN "name";
|
|
```
|
|
|
|
#### 3. Database Restore (Last Resort)
|
|
|
|
```bash
|
|
# Restore from backup
|
|
cat backup-20260213.sql | docker compose exec -T v2-postgres psql -U changemaker_v2 changemaker_v2_db
|
|
|
|
# Mark migrations as rolled back
|
|
docker compose exec v2-postgres psql -U changemaker_v2 -d changemaker_v2_db -c "
|
|
DELETE FROM _prisma_migrations
|
|
WHERE migration_name = '20260213123456_add_user_name';
|
|
"
|
|
```
|
|
|
|
#### 4. Reset Development Database
|
|
|
|
**WARNING: Deletes all data!**
|
|
|
|
```bash
|
|
cd api
|
|
npx prisma migrate reset
|
|
```
|
|
|
|
This:
|
|
1. Drops all tables
|
|
2. Re-applies all migrations from scratch
|
|
3. Runs seed script
|
|
|
|
### Handling Migration Conflicts
|
|
|
|
#### Schema Drift
|
|
|
|
**Problem:** Database schema doesn't match Prisma schema.
|
|
|
|
**Symptoms:**
|
|
```
|
|
Error: Database schema is not in sync with the migration history
|
|
```
|
|
|
|
**Solution:**
|
|
|
|
```bash
|
|
# Check what's different
|
|
npx prisma migrate diff \
|
|
--from-schema-datamodel prisma/schema.prisma \
|
|
--to-schema-datasource prisma/schema.prisma
|
|
|
|
# Create migration to fix drift
|
|
npx prisma migrate dev --name fix_schema_drift
|
|
```
|
|
|
|
#### Failed Migration
|
|
|
|
**Problem:** Migration fails during apply.
|
|
|
|
**Symptoms:**
|
|
```
|
|
Error: Migration failed with error:
|
|
ALTER TABLE "users" ADD COLUMN "age" INTEGER NOT NULL;
|
|
ERROR: column "age" contains null values
|
|
```
|
|
|
|
**Solution:**
|
|
|
|
```bash
|
|
# 1. Mark migration as rolled back
|
|
npx prisma migrate resolve --rolled-back 20260213123456_add_user_age
|
|
|
|
# 2. Fix migration SQL manually
|
|
vi prisma/migrations/20260213123456_add_user_age/migration.sql
|
|
|
|
# Change to:
|
|
ALTER TABLE "users" ADD COLUMN "age" INTEGER; -- Make nullable first
|
|
UPDATE "users" SET "age" = 0 WHERE "age" IS NULL; -- Set default
|
|
ALTER TABLE "users" ALTER COLUMN "age" SET NOT NULL; -- Then make required
|
|
|
|
# 3. Apply migration again
|
|
npx prisma migrate deploy
|
|
```
|
|
|
|
#### Conflicting Migrations (Team Environment)
|
|
|
|
**Problem:** Two developers create migrations simultaneously.
|
|
|
|
**Solution:**
|
|
|
|
```bash
|
|
# 1. Pull latest changes
|
|
git pull origin v2
|
|
|
|
# 2. Prisma detects conflict
|
|
npx prisma migrate dev
|
|
|
|
# 3. Resolve by creating merge migration
|
|
# Prisma will prompt you to create a migration that includes both changes
|
|
```
|
|
|
|
### Data Migrations
|
|
|
|
Prisma Migrate handles **schema** changes, not **data** changes. For data transformations:
|
|
|
|
#### Option 1: Custom SQL in Migration
|
|
|
|
Edit generated migration file:
|
|
|
|
```sql
|
|
-- Add column (Prisma-generated)
|
|
ALTER TABLE "users" ADD COLUMN "full_name" TEXT;
|
|
|
|
-- Populate from existing data (manual addition)
|
|
UPDATE "users" SET "full_name" = "first_name" || ' ' || "last_name";
|
|
|
|
-- Remove old columns (Prisma-generated)
|
|
ALTER TABLE "users" DROP COLUMN "first_name";
|
|
ALTER TABLE "users" DROP COLUMN "last_name";
|
|
```
|
|
|
|
#### Option 2: Separate Data Migration Script
|
|
|
|
```typescript
|
|
// api/prisma/data-migrations/20260213-populate-full-name.ts
|
|
import { PrismaClient } from '@prisma/client';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
async function main() {
|
|
const users = await prisma.user.findMany();
|
|
|
|
for (const user of users) {
|
|
await prisma.user.update({
|
|
where: { id: user.id },
|
|
data: {
|
|
fullName: `${user.firstName} ${user.lastName}`
|
|
}
|
|
});
|
|
}
|
|
|
|
console.log(`Updated ${users.length} users`);
|
|
}
|
|
|
|
main()
|
|
.catch(console.error)
|
|
.finally(() => prisma.$disconnect());
|
|
```
|
|
|
|
Run after migration:
|
|
|
|
```bash
|
|
npx tsx prisma/data-migrations/20260213-populate-full-name.ts
|
|
```
|
|
|
|
## Drizzle Push (Media API)
|
|
|
|
### Drizzle Overview
|
|
|
|
**Drizzle Kit Push:**
|
|
- Syncs schema directly to database
|
|
- No migration files generated
|
|
- Fast iteration for prototyping
|
|
- Used only for Media API tables
|
|
|
|
**Schema Location:**
|
|
- `api/src/modules/media/db/schema.ts`
|
|
|
|
**When to Use:**
|
|
- Rapid prototyping
|
|
- Development only
|
|
- Media API tables (videos, jobs, reactions)
|
|
|
|
**When NOT to Use:**
|
|
- Production deployments
|
|
- Main API tables (use Prisma)
|
|
- When migration history is needed
|
|
|
|
### Drizzle Push Workflow
|
|
|
|
#### Step 1: Edit Schema
|
|
|
|
Edit `api/src/modules/media/db/schema.ts`:
|
|
|
|
```typescript
|
|
// Before
|
|
export const videos = pgTable('videos', {
|
|
id: serial('id').primaryKey(),
|
|
filename: text('filename').notNull(),
|
|
title: text('title'),
|
|
duration: integer('duration'),
|
|
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
});
|
|
|
|
// After (add description field)
|
|
export const videos = pgTable('videos', {
|
|
id: serial('id').primaryKey(),
|
|
filename: text('filename').notNull(),
|
|
title: text('title'),
|
|
description: text('description'), // New field
|
|
duration: integer('duration'),
|
|
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
});
|
|
```
|
|
|
|
#### Step 2: Push Schema
|
|
|
|
```bash
|
|
cd api
|
|
npm run drizzle:push
|
|
```
|
|
|
|
Or directly:
|
|
|
|
```bash
|
|
cd api
|
|
npx drizzle-kit push
|
|
```
|
|
|
|
**What happens:**
|
|
1. Drizzle compares schema to database
|
|
2. Generates SQL for changes
|
|
3. Applies changes immediately
|
|
4. No migration files created
|
|
|
|
**Expected output:**
|
|
```
|
|
Reading config from drizzle.config.ts
|
|
Using 'pg' driver for database querying
|
|
|
|
Pulling schema from database...
|
|
[✓] Schema pulled successfully
|
|
|
|
Comparing schemas...
|
|
[!] Changes detected:
|
|
- ALTER TABLE "videos" ADD COLUMN "description" TEXT;
|
|
|
|
Do you want to execute these changes? [y/N]: y
|
|
|
|
Applying changes...
|
|
[✓] Schema pushed successfully
|
|
```
|
|
|
|
#### Step 3: Verify Changes
|
|
|
|
```bash
|
|
# Check with Drizzle Studio
|
|
cd api
|
|
npx drizzle-kit studio
|
|
```
|
|
|
|
Or query directly:
|
|
|
|
```bash
|
|
docker compose exec v2-postgres psql -U changemaker_v2 -d changemaker_v2_db -c "\d videos"
|
|
```
|
|
|
|
### Drizzle Best Practices
|
|
|
|
#### 1. Development Only
|
|
|
|
Use Drizzle Push only in development:
|
|
|
|
**Good:**
|
|
```bash
|
|
# Development
|
|
npm run drizzle:push
|
|
```
|
|
|
|
**Bad:**
|
|
```bash
|
|
# Production (use Prisma migrate for production schema changes)
|
|
npm run drizzle:push
|
|
```
|
|
|
|
#### 2. Backup Before Push
|
|
|
|
Always backup before pushing schema:
|
|
|
|
```bash
|
|
# Backup database
|
|
docker compose exec v2-postgres pg_dump -U changemaker_v2 changemaker_v2_db > backup.sql
|
|
|
|
# Push schema
|
|
npm run drizzle:push
|
|
|
|
# If something breaks, restore:
|
|
cat backup.sql | docker compose exec -T v2-postgres psql -U changemaker_v2 changemaker_v2_db
|
|
```
|
|
|
|
#### 3. Test Changes Locally
|
|
|
|
Never push untested schema changes:
|
|
|
|
```bash
|
|
# 1. Edit schema
|
|
vi src/modules/media/db/schema.ts
|
|
|
|
# 2. Push to dev database
|
|
npm run drizzle:push
|
|
|
|
# 3. Test with Drizzle Studio
|
|
npm run drizzle:studio
|
|
|
|
# 4. Test API endpoints
|
|
curl http://localhost:4100/api/media/videos
|
|
```
|
|
|
|
### Drizzle vs Prisma
|
|
|
|
| Feature | Prisma Migrate | Drizzle Push |
|
|
|---------|----------------|--------------|
|
|
| Migration files | ✅ Yes | ❌ No |
|
|
| Migration history | ✅ Tracked | ❌ Not tracked |
|
|
| Rollback | ✅ Via version control | ❌ Manual only |
|
|
| Production use | ✅ Recommended | ⚠️ Not recommended |
|
|
| Prototyping | ⚠️ Slower | ✅ Faster |
|
|
| Use case | Main API tables | Media API tables |
|
|
|
|
## Seeding After Migration
|
|
|
|
### Running Seed Script
|
|
|
|
After migrations, seed database:
|
|
|
|
```bash
|
|
cd api
|
|
npx prisma db seed
|
|
```
|
|
|
|
**What it does:**
|
|
- Runs `prisma/seed.ts`
|
|
- Creates admin user
|
|
- Creates default settings
|
|
- Creates sample blocks
|
|
|
|
**Expected output:**
|
|
```
|
|
Running seed command `tsx prisma/seed.ts` ...
|
|
Seeding database...
|
|
Created default settings
|
|
Created admin user: admin@example.com
|
|
Created 10 sample blocks
|
|
Seed completed successfully
|
|
```
|
|
|
|
### Custom Seed Data
|
|
|
|
Edit `api/prisma/seed.ts`:
|
|
|
|
```typescript
|
|
import { PrismaClient } from '@prisma/client';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
async function main() {
|
|
// Create admin user
|
|
await prisma.user.upsert({
|
|
where: { email: 'admin@example.com' },
|
|
update: {},
|
|
create: {
|
|
email: 'admin@example.com',
|
|
password: await hashPassword('Admin123!'),
|
|
role: 'SUPER_ADMIN',
|
|
name: 'Admin User'
|
|
}
|
|
});
|
|
|
|
// Create sample campaign
|
|
await prisma.campaign.create({
|
|
data: {
|
|
title: 'Sample Campaign',
|
|
description: 'This is a sample campaign',
|
|
active: true,
|
|
createdByUserId: 1
|
|
}
|
|
});
|
|
|
|
console.log('Seed completed');
|
|
}
|
|
|
|
main()
|
|
.catch(console.error)
|
|
.finally(() => prisma.$disconnect());
|
|
```
|
|
|
|
## CI/CD Integration
|
|
|
|
### GitHub Actions Example
|
|
|
|
```yaml
|
|
name: Deploy
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
|
|
jobs:
|
|
deploy:
|
|
runs-on: ubuntu-latest
|
|
steps:
|
|
- uses: actions/checkout@v3
|
|
|
|
- name: Setup Node.js
|
|
uses: actions/setup-node@v3
|
|
with:
|
|
node-version: '20'
|
|
|
|
- name: Install dependencies
|
|
working-directory: ./api
|
|
run: npm ci
|
|
|
|
- name: Run migrations
|
|
working-directory: ./api
|
|
env:
|
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
run: npx prisma migrate deploy
|
|
|
|
- name: Seed database
|
|
working-directory: ./api
|
|
env:
|
|
DATABASE_URL: ${{ secrets.DATABASE_URL }}
|
|
run: npx prisma db seed
|
|
```
|
|
|
|
### Docker Deployment
|
|
|
|
```dockerfile
|
|
# api/Dockerfile
|
|
FROM node:20-alpine
|
|
|
|
WORKDIR /app
|
|
|
|
COPY package*.json ./
|
|
RUN npm ci --production
|
|
|
|
COPY . .
|
|
|
|
# Generate Prisma Client
|
|
RUN npx prisma generate
|
|
|
|
# Run migrations on startup
|
|
CMD npx prisma migrate deploy && npm start
|
|
```
|
|
|
|
## Troubleshooting
|
|
|
|
### Migration Fails with "Column Already Exists"
|
|
|
|
**Problem:**
|
|
```
|
|
Error: column "name" of relation "users" already exists
|
|
```
|
|
|
|
**Solution:**
|
|
|
|
```bash
|
|
# Mark migration as applied
|
|
npx prisma migrate resolve --applied 20260213123456_add_user_name
|
|
|
|
# Or drop column manually and re-run
|
|
docker compose exec v2-postgres psql -U changemaker_v2 -d changemaker_v2_db -c "ALTER TABLE users DROP COLUMN name;"
|
|
npx prisma migrate deploy
|
|
```
|
|
|
|
### Migration Fails with "Relation Does Not Exist"
|
|
|
|
**Problem:**
|
|
```
|
|
Error: relation "posts" does not exist
|
|
```
|
|
|
|
**Solution:**
|
|
|
|
```bash
|
|
# Check migration history
|
|
npx prisma migrate status
|
|
|
|
# Apply missing migrations
|
|
npx prisma migrate deploy
|
|
|
|
# Or reset (development only)
|
|
npx prisma migrate reset
|
|
```
|
|
|
|
### Schema Out of Sync
|
|
|
|
**Problem:**
|
|
```
|
|
Error: Database schema is not in sync
|
|
```
|
|
|
|
**Solution:**
|
|
|
|
```bash
|
|
# Generate migration to fix drift
|
|
npx prisma migrate dev --name fix_drift
|
|
|
|
# Or in production, create explicit migration
|
|
npx prisma migrate diff \
|
|
--from-schema-datamodel prisma/schema.prisma \
|
|
--to-schema-datasource prisma/schema.prisma \
|
|
--script > fix-drift.sql
|
|
|
|
# Review fix-drift.sql and apply manually
|
|
```
|
|
|
|
### Drizzle Push Fails
|
|
|
|
**Problem:**
|
|
```
|
|
Error: Could not push schema
|
|
```
|
|
|
|
**Solution:**
|
|
|
|
```bash
|
|
# Check Drizzle config
|
|
cat api/drizzle.config.ts
|
|
|
|
# Verify DATABASE_URL
|
|
echo $DATABASE_URL
|
|
|
|
# Test database connection
|
|
docker compose exec v2-postgres psql -U changemaker_v2 -d changemaker_v2_db -c "SELECT 1"
|
|
|
|
# Clear Drizzle cache and retry
|
|
rm -rf api/.drizzle
|
|
npm run drizzle:push
|
|
```
|
|
|
|
## Related Documentation
|
|
|
|
- **Setup:** [Local Development Setup](local-setup.md)
|
|
- **Commands:** [NPM Commands Reference](npm-commands.md)
|
|
- **Docker:** [Docker Workflow](docker-workflow.md)
|
|
- **Database:** [Database Schema](../architecture/database-schema.md)
|
|
- **Deployment:** [Production Deployment](../deployment/production.md)
|
|
|
|
## Summary
|
|
|
|
You now know:
|
|
- ✅ How Prisma Migrate tracks schema changes
|
|
- ✅ How to create and apply migrations
|
|
- ✅ Common migration scenarios (add field, table, relation)
|
|
- ✅ Migration best practices
|
|
- ✅ How to handle migration conflicts
|
|
- ✅ How to perform data migrations
|
|
- ✅ How Drizzle Push works for Media API
|
|
- ✅ When to use Prisma vs Drizzle
|
|
- ✅ How to seed database after migrations
|
|
- ✅ How to integrate migrations in CI/CD
|
|
|
|
**Quick Reference:**
|
|
```bash
|
|
# Prisma: Create migration
|
|
npx prisma migrate dev --name description
|
|
|
|
# Prisma: Apply migrations (production)
|
|
npx prisma migrate deploy
|
|
|
|
# Prisma: Check status
|
|
npx prisma migrate status
|
|
|
|
# Drizzle: Push schema (dev only)
|
|
npx drizzle-kit push
|
|
|
|
# Seed database
|
|
npx prisma db seed
|
|
|
|
# Reset (dev only, DELETES DATA)
|
|
npx prisma migrate reset
|
|
```
|