prisma-migrations · git:20260610.47ec162 · 2026-06-10 · sha256 9b8916d40bb21093
prisma-migrations git:20260610.47ec162A
Immutable. This exact content never changes and is served at /api/v1/blob/9b8916d40bb21093.
---
name: prisma-migrations
description: Best practices for Prisma schema changes, migrations, seeding, and rollback in a NestJS + TypeScript backend
---
# Prisma Migrations Skill
## Scope
Apply this skill for any task involving database schema changes, migration creation, seed data management, or data transformations. This skill assumes **Prisma ORM** with **PostgreSQL** in a **NestJS + TypeScript** project.
---
## Schema as Source of Truth
The file `prisma/schema.prisma` is the single source of truth for all database models, relations, enums, and indexes. Never modify the database directly — all changes flow through the schema file and Prisma's migration tooling.
---
## Migration Workflow
### When to Use `prisma migrate dev`
Use `prisma migrate dev` for all schema changes that will reach production. This generates a SQL migration file, applies it to the dev database, and regenerates the Prisma Client.
```bash
# 1. Edit prisma/schema.prisma
# 2. Generate and apply migration
npx prisma migrate dev --name <descriptive-name>
```
### When to Use `prisma db push`
Use `prisma db push` **only** during early prototyping or when iterating on a schema that has not been committed yet. It applies schema changes without creating a migration file — meaning the change is not reproducible.
**Never use `db push` on a schema that has existing migrations in version control.** It will drift the database from the migration history.
### Migration Naming
Migration names must be descriptive, lowercase, and use hyphens:
| Change | Migration Name |
|---|---|
| Add a new model | `add-audit-log-table` |
| Add a field to existing model | `add-last-login-to-user` |
| Add an index | `add-index-on-user-email` |
| Remove a field | `remove-deprecated-role-field` |
| Add an enum | `add-permission-action-enum` |
| Rename a field | `rename-name-to-full-name-on-user` |
### Migration File Review
After generating a migration, **always read the generated SQL file** in `prisma/migrations/<timestamp>_<name>/migration.sql` before committing. Verify:
- [ ] The SQL matches your intent — no accidental drops or renames
- [ ] Indexes are created where expected
- [ ] Default values are applied correctly
- [ ] Foreign key constraints and cascades are correct
- [ ] No data loss — columns are not dropped that contain data
---
## Destructive Changes
Destructive changes require extra care because they are irreversible once applied to production.
### Dropping a Column
1. First deploy: remove all code references to the column, but keep the column in the schema
2. Second deploy: remove the column from the schema and generate the migration
3. This two-phase approach prevents runtime errors from code that still references the column
### Renaming a Column
Prisma treats a rename as a drop + create. To preserve data:
1. Add the new column with a migration
2. Write a data migration to copy values from old to new column
3. Update all code references to use the new column
4. Drop the old column in a subsequent migration
### Dropping a Table
Verify that no code references the table before generating the migration. Check:
- Repositories, services, resolvers that query the table
- Seed files that insert into the table
- Test factories that reference the model
---
## Data Migrations
Schema migrations (DDL) and data migrations (DML) should be separate:
- **Schema migration**: generated by Prisma, modifies table structure
- **Data migration**: a TypeScript script in `prisma/data-migrations/` that transforms existing data
```typescript
// prisma/data-migrations/001-backfill-user-display-name.ts
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const users = await prisma.user.findMany({
where: { displayName: null },
});
for (const user of users) {
await prisma.user.update({
where: { id: user.id },
data: { displayName: `${user.firstName} ${user.lastName}` },
});
}
console.log(`Backfilled displayName for ${users.length} users`);
}
main()
.catch(console.error)
.finally(() => prisma.$disconnect());
```
Run data migrations manually after the schema migration is applied. Never embed data transformations inside Prisma's generated SQL migration files.
---
## Seeding
### Seed File Location
The seed script lives at `prisma/seed.ts` and is configured in `package.json`:
```json
{
"prisma": {
"seed": "ts-node prisma/seed.ts"
}
}
```
### Seed Design Rules
- Seeds must be **idempotent** — running them multiple times produces the same result
- Use `upsert` instead of `create` to avoid duplicate key errors on re-runs
- Seeds for different concerns (roles, permissions, test data) should be organized into separate functions
- Never seed production-only data in development — use environment checks
- Seed data must match the current schema — update seeds when the schema changes
```typescript
async function seedRoles(prisma: PrismaClient) {
const roles = [
{ name: 'admin', description: 'Full system access' },
{ name: 'user', description: 'Standard user access' },
];
for (const role of roles) {
await prisma.role.upsert({
where: { name: role.name },
update: { description: role.description },
create: role,
});
}
}
```
### Running Seeds
```bash
# Run seed script
npx prisma db seed
# Reset database and re-seed (development only)
npx prisma migrate reset
```
---
## Testing Database
### Test Database Reset
For integration tests, reset the database to a known state before each test suite:
```typescript
// test/setup-e2e.ts
beforeAll(async () => {
// Apply all migrations to test database
execSync('npx prisma migrate deploy', {
env: { ...process.env, DATABASE_URL: process.env.TEST_DATABASE_URL },
});
});
afterEach(async () => {
// Clean up test data — use transactions for speed
await prisma.$transaction([
prisma.userRole.deleteMany(),
prisma.user.deleteMany(),
prisma.role.deleteMany(),
]);
});
```
### Test Database Isolation
- Each test suite uses a dedicated test database or a transaction that is rolled back
- Test database URL is configured via `DATABASE_URL` in `.env.test`
- Never run tests against the development database — data will be destroyed
---
## Rollback Procedures
### Development Rollback
```bash
# Roll back the last migration (marks it as rolled back, does not delete the file)
npx prisma migrate resolve --rolled-back <migration-name>
# Then apply a corrective migration
npx prisma migrate dev --name fix-<description>
```
### Production Rollback
Prisma does not support automatic rollback. For production:
1. Write a new migration that reverses the changes
2. Test the reverse migration against a staging database
3. Apply the reverse migration to production
**Never delete migration files from the `prisma/migrations/` directory** — this breaks the migration history and makes `prisma migrate deploy` fail.
---
## Hard Rules
- Never use `prisma db push` on a branch that has committed migrations
- Never edit a generated migration SQL file after it has been applied to any environment
- Never delete migration files — add new migrations to fix mistakes
- Always read the generated SQL before committing a migration
- Always update seed files when the schema changes
- Always use two-phase deploys for destructive column/table changes in production
- Never run `prisma migrate reset` in production — it drops and recreates the entire database