Prisma Migrate — Complete Guide
In this tutorial, you will learn about Prisma Migrate. We cover key concepts, practical examples, and best practices to help you master this topic.
Learn Prisma Migrate: define declarative schema, auto-generate migrations, apply and rollback, seed databases, integrate with Node.js, and best practices for Prisma schema evolution in TypeScript applications.
What You Learn
You will learn how to use Prisma Migrate for database migrations: define a declarative schema in schema.prisma, auto-generate migrations from schema changes, apply and rollback migrations, seed databases, and integrate Prisma with Node.js applications.
Why It Matters
Prisma offers a unique declarative approach. You define the desired schema in a single file. Prisma generates migrations to get there. This is simpler than writing Migration files for every change. Prisma Migrate is the standard for modern TypeScript backends.
Real-World Use
DodaTech's TypeScript API uses Prisma with PostgreSQL. The schema.prisma file defines all 30+ models. Prisma Migrate generates migrations from schema changes. The Prisma Client provides type-safe database access. Schema changes are reviewed as PRs to schema.prisma.
Schema Definition
// schema.prisma - Declarative schema
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
phone String?
isActive Boolean @default(true) @map("is_active")
orders Order[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("users")
}
model Order {
id Int @id @default(autoincrement())
userId Int @map("user_id")
user User @relation(fields: [userId], references: [id])
total Decimal @db.Decimal(10, 2)
status String @default("pending")
items OrderItem[]
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("orders")
}
model OrderItem {
id Int @id @default(autoincrement())
orderId Int @map("order_id")
order Order @relation(fields: [orderId], references: [id])
product String
quantity Int
price Decimal @db.Decimal(10, 2)
@@map("order_items")
}
Expected output: Schema.prisma defines models, fields, types, relations, indexes, and database mappings. @map and @@map control database column and table names. The schema is the single source of truth.
Generating Migrations
# After changing schema.prisma, create a migration
npx prisma migrate dev --name add_phone_to_users
# Output:
# Environment variables loaded from .env
# Prisma schema loaded from prisma/schema.prisma
# Datasource "db": PostgreSQL database "app_dev"
#
# Applying migration `20260628120000_add_phone_to_users`
#
# Your database is now in sync with your schema.
#
# ✔ Created migration `20260628120000_add_phone_to_users`
# Migration file created:
# prisma/migrations/20260628120000_add_phone_to_users/migration.sql
-- prisma/migrations/20260628120000_add_phone_to_users/migration.sql
-- AlterTable
ALTER TABLE "users" ADD COLUMN "phone" TEXT;
Expected output: Prisma Migrate generates a SQL migration file from schema changes. The migration is applied immediately in development. The migration.sql file is committed to version control.
Applying and Rolling Back
# Apply pending migrations in production
npx prisma migrate deploy
# Reset database (drops all data)
npx prisma migrate reset
# Create migration without applying (for review)
npx prisma migrate dev --create-only --name migration_name
# Rollback in development
npx prisma migrate dev --name previous_migration_name
# Production deployment flow:
# 1. Create migration locally
npx prisma migrate dev --name add_user_role
# 2. Commit migration file to git
git add prisma/migrations/
git commit -m "Add role field to User model"
# 3. Deploy: run migrations on production
npx prisma migrate deploy
# 4. Generate client for new schema
npx prisma generate
Expected output: migrate dev creates and applies migrations in development. migrate deploy applies pending migrations in production. migrate reset drops and recreates the database. migrate deploy is idempotent.
Seeding
// schema.prisma - add seed configuration
// Already in schema.prisma:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// prisma/seed.js
const { PrismaClient } = require('@prisma/client');
const prisma = new PrismaClient();
async function main() {
// Create users
const alice = await prisma.user.create({
data: {
email: 'alice@example.com',
name: 'Alice Johnson',
orders: {
create: [
{ total: 59.99, status: 'delivered' },
{ total: 29.99, status: 'pending' },
],
},
},
});
const bob = await prisma.user.create({
data: {
email: 'bob@example.com',
name: 'Bob Smith',
orders: {
create: [
{ total: 99.99, status: 'paid' },
],
},
},
});
console.log('Seeded:', { alice: alice.id, bob: bob.id });
}
main()
.catch((e) => {
console.error(e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});
// package.json
{
"prisma": {
"seed": "node prisma/seed.js"
}
}
# Run seed
npx prisma db seed
Expected output: Seed script uses Prisma Client to create initial data. The seed is configured in package.json. npx prisma db seed runs the seed. Seeds are idempotent (upsert or check before create).
Client Generation
# Generate Prisma Client for the current schema
npx prisma generate
# The generated client provides type-safe database access
// app.ts - Using Prisma Client
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function getUsersWithOrders() {
const users = await prisma.user.findMany({
include: {
orders: {
include: {
items: true,
},
},
},
where: {
isActive: true,
},
});
return users;
}
async function createOrder(userId: number, total: number) {
const order = await prisma.order.create({
data: {
userId,
total,
status: 'pending',
},
});
return order;
}
// Type-safe: prisma.user.findMany() returns User[]
// Type-safe: prisma.order.create() validates input types
Expected output: prisma generate creates a TypeScript client with full type safety. Autocompletion for models, fields, and relations. Compile-time validation prevents database query errors.
Migration History
# View migration history
npx prisma migrate status
# Output:
# Prisma Migrate is managing the following migrations:
#
# 20260628120000_init
# 20260628130000_add_phone_to_users
# 20260628140000_create_orders
# 20260628150000_add_order_items
#
# The following migration have been applied:
#
# 20260628120000_init
# 20260628130000_add_phone_to_users
#
# The following migrations have not been applied:
#
# 20260628140000_create_orders
# 20260628150000_add_order_items
Expected output: migrate status shows the migration history, which migrations are applied, and which are pending. This is useful for CI/CD pipeline verification.
Common Mistakes
1. Editing Migration SQL Files Directly
Prisma generates migration SQL from schema changes. Editing the SQL creates inconsistency between the schema and the migration. If you need custom SQL, use prisma migrate dev --create-only and add SQL before applying.
2. Not Running migrate deploy in Production
Using migrate dev in production is dangerous. migrate dev may reset the database. Always use migrate deploy in production. It only applies pending migrations without modification.
3. Forgetting prisma generate After Migration
After running migrations, you must run prisma generate to update the Prisma Client. The client reflects the new schema. CI/CD pipelines should run migrate deploy then prisma generate.
4. Using Prisma Migrate with an Existing Database
Prisma Migrate expects to manage the full schema. For existing databases, use prisma migrate diff to generate an initial migration from the database. Alternatively, use prisma db pull to introspect.
5. No Seed Idempotency
Seeds that create duplicate records fail on second run. Use upsert or check for existing records before creating. This allows seeds to be run multiple times safely.
Practice Questions
1. How does Prisma Migrate differ from traditional migration tools?
Prisma uses a declarative approach. You define the desired schema in schema.prisma. Prisma generates migrations to match. Traditional tools require writing migration files manually for each change.
2. What is the difference between migrate dev and migrate deploy?
migrate dev is for development: creates and applies migrations, can reset the database. migrate deploy is for production: applies pending migrations without modification, safe for automated deployment.
3. Why must prisma generate be run after migrations?
prisma generate updates the Prisma Client with the new schema. Without it, the client still reflects the old schema. TypeScript types, queries, and validation would be incorrect.
4. How do you handle custom SQL in Prisma migrations?
Use migrate dev --create-only to create the migration file without applying it. Add custom SQL to the generated migration.sql file. Then run migrate dev to apply. The custom SQL is preserved.
Challenge
Set up Prisma for a TypeScript application with: User, Post, and Comment models with relations, migrations for all models, a custom migration with raw SQL for creating a full-text search index, seed script that creates sample data, and deployment script that runs migrate deploy and prisma generate.
FAQ
Mini Project: Prisma Migration Setup
Set up Prisma for a TypeScript application with: PostgreSQL database, User and Profile models (1:1 relation), Post and Category models (M:N relation), migrations for all models, custom SQL migration for a search index, seed script with sample data, type-safe queries using Prisma Client, and deployment script for CI/CD.
What's Next
Now that you understand Prisma Migrate, learn about Migration Workflow for structuring migration development in team environments.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro