312 lines
5.9 KiB
Markdown
312 lines
5.9 KiB
Markdown
|
|
---
|
||
|
|
description: How to create and run database migrations
|
||
|
|
---
|
||
|
|
|
||
|
|
# Database Migration Workflow
|
||
|
|
|
||
|
|
Follow these steps to create and apply database migrations using Prisma.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Step 1: Update Prisma Schema
|
||
|
|
|
||
|
|
Edit `prisma/schema.prisma`:
|
||
|
|
|
||
|
|
```prisma
|
||
|
|
model Course {
|
||
|
|
id Int @id @default(autoincrement())
|
||
|
|
title Json // { th: "", en: "" }
|
||
|
|
description Json
|
||
|
|
price Decimal @db.Decimal(10, 2)
|
||
|
|
is_free Boolean @default(false)
|
||
|
|
have_certificate Boolean @default(false)
|
||
|
|
status CourseStatus @default(DRAFT)
|
||
|
|
thumbnail String?
|
||
|
|
|
||
|
|
category_id Int
|
||
|
|
category Category @relation(fields: [category_id], references: [id])
|
||
|
|
|
||
|
|
created_by Int
|
||
|
|
creator User @relation(fields: [created_by], references: [id])
|
||
|
|
|
||
|
|
chapters Chapter[]
|
||
|
|
instructors CourseInstructor[]
|
||
|
|
enrollments Enrollment[]
|
||
|
|
|
||
|
|
created_at DateTime @default(now())
|
||
|
|
updated_at DateTime @updatedAt
|
||
|
|
deleted_at DateTime?
|
||
|
|
is_deleted Boolean @default(false)
|
||
|
|
|
||
|
|
@@index([category_id])
|
||
|
|
@@index([status])
|
||
|
|
@@index([is_deleted])
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Step 2: Create Migration
|
||
|
|
|
||
|
|
// turbo
|
||
|
|
Run migration command:
|
||
|
|
```bash
|
||
|
|
npx prisma migrate dev --name add_course_model
|
||
|
|
```
|
||
|
|
|
||
|
|
This will:
|
||
|
|
1. Generate SQL migration file
|
||
|
|
2. Apply migration to database
|
||
|
|
3. Regenerate Prisma Client
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Step 3: Review Migration File
|
||
|
|
|
||
|
|
Check generated SQL in `prisma/migrations/`:
|
||
|
|
|
||
|
|
```sql
|
||
|
|
-- CreateTable
|
||
|
|
CREATE TABLE "Course" (
|
||
|
|
"id" SERIAL NOT NULL,
|
||
|
|
"title" JSONB NOT NULL,
|
||
|
|
"description" JSONB NOT NULL,
|
||
|
|
"price" DECIMAL(10,2) NOT NULL,
|
||
|
|
"is_free" BOOLEAN NOT NULL DEFAULT false,
|
||
|
|
...
|
||
|
|
CONSTRAINT "Course_pkey" PRIMARY KEY ("id")
|
||
|
|
);
|
||
|
|
|
||
|
|
-- CreateIndex
|
||
|
|
CREATE INDEX "Course_category_id_idx" ON "Course"("category_id");
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Step 4: Update Prisma Client
|
||
|
|
|
||
|
|
// turbo
|
||
|
|
Regenerate client if needed:
|
||
|
|
```bash
|
||
|
|
npx prisma generate
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Step 5: Create Seed Data (Optional)
|
||
|
|
|
||
|
|
Update `prisma/seed.js`:
|
||
|
|
|
||
|
|
```javascript
|
||
|
|
const { PrismaClient } = require('@prisma/client');
|
||
|
|
const prisma = new PrismaClient();
|
||
|
|
|
||
|
|
async function main() {
|
||
|
|
// Create categories
|
||
|
|
const category = await prisma.category.create({
|
||
|
|
data: {
|
||
|
|
name: { th: 'การเขียนโปรแกรม', en: 'Programming' },
|
||
|
|
description: { th: 'หลักสูตรด้านการเขียนโปรแกรม', en: 'Programming courses' }
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Create test user
|
||
|
|
const user = await prisma.user.create({
|
||
|
|
data: {
|
||
|
|
username: 'instructor1',
|
||
|
|
email: 'instructor@example.com',
|
||
|
|
password: '$2b$10$...', // hashed password
|
||
|
|
role_id: 2 // INSTRUCTOR
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
// Create test course
|
||
|
|
const course = await prisma.course.create({
|
||
|
|
data: {
|
||
|
|
title: { th: 'Python สำหรับผู้เริ่มต้น', en: 'Python for Beginners' },
|
||
|
|
description: { th: 'เรียนรู้ Python', en: 'Learn Python' },
|
||
|
|
price: 990,
|
||
|
|
is_free: false,
|
||
|
|
category_id: category.id,
|
||
|
|
created_by: user.id
|
||
|
|
}
|
||
|
|
});
|
||
|
|
|
||
|
|
console.log('Seed data created:', { category, user, course });
|
||
|
|
}
|
||
|
|
|
||
|
|
main()
|
||
|
|
.catch((e) => {
|
||
|
|
console.error(e);
|
||
|
|
process.exit(1);
|
||
|
|
})
|
||
|
|
.finally(async () => {
|
||
|
|
await prisma.$disconnect();
|
||
|
|
});
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Step 6: Run Seed
|
||
|
|
|
||
|
|
// turbo
|
||
|
|
Seed the database:
|
||
|
|
```bash
|
||
|
|
npx prisma db seed
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Common Migration Tasks
|
||
|
|
|
||
|
|
### Add New Field
|
||
|
|
```prisma
|
||
|
|
model Course {
|
||
|
|
// ... existing fields
|
||
|
|
slug String @unique // New field
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
// turbo
|
||
|
|
```bash
|
||
|
|
npx prisma migrate dev --name add_course_slug
|
||
|
|
```
|
||
|
|
|
||
|
|
### Add Index
|
||
|
|
```prisma
|
||
|
|
model Course {
|
||
|
|
// ... existing fields
|
||
|
|
|
||
|
|
@@index([created_at]) // New index
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Add Relation
|
||
|
|
```prisma
|
||
|
|
model Lesson {
|
||
|
|
// ... existing fields
|
||
|
|
|
||
|
|
quiz_id Int?
|
||
|
|
quiz Quiz? @relation(fields: [quiz_id], references: [id])
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Rename Field
|
||
|
|
```prisma
|
||
|
|
model Course {
|
||
|
|
// Old: instructor_id
|
||
|
|
created_by Int // New name
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Note**: Prisma will detect rename and ask for confirmation
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Production Migration
|
||
|
|
|
||
|
|
### Step 1: Generate Migration (Dev)
|
||
|
|
```bash
|
||
|
|
npx prisma migrate dev --name migration_name
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 2: Commit Migration Files
|
||
|
|
```bash
|
||
|
|
git add prisma/migrations/
|
||
|
|
git commit -m "Add migration: migration_name"
|
||
|
|
```
|
||
|
|
|
||
|
|
### Step 3: Deploy to Production
|
||
|
|
```bash
|
||
|
|
npx prisma migrate deploy
|
||
|
|
```
|
||
|
|
|
||
|
|
**Important**: Never use `migrate dev` in production!
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Rollback Migration
|
||
|
|
|
||
|
|
### Option 1: Manual Rollback
|
||
|
|
```bash
|
||
|
|
# Find migration to rollback
|
||
|
|
ls prisma/migrations/
|
||
|
|
|
||
|
|
# Manually run the down migration (if exists)
|
||
|
|
psql $DATABASE_URL < prisma/migrations/XXXXXX_migration_name/down.sql
|
||
|
|
```
|
||
|
|
|
||
|
|
### Option 2: Reset Database (Dev Only)
|
||
|
|
```bash
|
||
|
|
npx prisma migrate reset
|
||
|
|
```
|
||
|
|
|
||
|
|
**Warning**: This will delete all data!
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Troubleshooting
|
||
|
|
|
||
|
|
### Migration Failed
|
||
|
|
```bash
|
||
|
|
# Check database status
|
||
|
|
npx prisma migrate status
|
||
|
|
|
||
|
|
# Resolve migration
|
||
|
|
npx prisma migrate resolve --applied MIGRATION_NAME
|
||
|
|
# or
|
||
|
|
npx prisma migrate resolve --rolled-back MIGRATION_NAME
|
||
|
|
```
|
||
|
|
|
||
|
|
### Schema Drift
|
||
|
|
```bash
|
||
|
|
# Check for drift
|
||
|
|
npx prisma migrate diff
|
||
|
|
|
||
|
|
# Reset and reapply
|
||
|
|
npx prisma migrate reset
|
||
|
|
```
|
||
|
|
|
||
|
|
### Generate Client Error
|
||
|
|
```bash
|
||
|
|
# Clear node_modules and reinstall
|
||
|
|
rm -rf node_modules
|
||
|
|
npm install
|
||
|
|
|
||
|
|
# Regenerate client
|
||
|
|
npx prisma generate
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Checklist
|
||
|
|
|
||
|
|
- [ ] Schema updated in `prisma/schema.prisma`
|
||
|
|
- [ ] Migration created with descriptive name
|
||
|
|
- [ ] Migration SQL reviewed
|
||
|
|
- [ ] Migration applied successfully
|
||
|
|
- [ ] Prisma Client regenerated
|
||
|
|
- [ ] Seed data updated (if needed)
|
||
|
|
- [ ] Tests updated for new schema
|
||
|
|
- [ ] Migration files committed to git
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Best Practices
|
||
|
|
|
||
|
|
1. **Descriptive Names**: Use clear migration names
|
||
|
|
- ✅ `add_course_certificate_field`
|
||
|
|
- ❌ `update_schema`
|
||
|
|
|
||
|
|
2. **Small Migrations**: One logical change per migration
|
||
|
|
|
||
|
|
3. **Review SQL**: Always check generated SQL before applying
|
||
|
|
|
||
|
|
4. **Test First**: Test migrations on dev database first
|
||
|
|
|
||
|
|
5. **Backup**: Backup production database before migrating
|
||
|
|
|
||
|
|
6. **Indexes**: Add indexes for frequently queried fields
|
||
|
|
|
||
|
|
7. **Constraints**: Use database constraints for data integrity
|