Your First Migration
Objectives
By the end of this lesson, you should be able to:
- Run
prisma migrate devand explain what it does - Locate and read a generated migration file
- Confirm a migration actually created a table in the database
๐ก Why this matters: Every model written so far exists only in
schema.prisma, nothing has touched the actual database. This lesson closes that gap, turning a schema into real tables, tracked and versioned.
โ ๏ธ A note on verification: as throughout this module, the Prisma CLI can’t run inside this course’s own sandboxed tooling (no route to its engine download). The commands and output below reflect Prisma’s stable, current, documented behavior. Run them yourself, on your own machine, against your own database, they’ll behave exactly as shown.
Running the Migration
Starting from the Student model in the last lesson:
npx prisma migrate dev --name init
Environment variables loaded from .env
Prisma schema loaded from prisma/schema.prisma
Datasource "db": PostgreSQL database "school", schema "public" at "localhost:5432"
Applying migration `20260101120000_init`
The following migration(s) have been created and applied from new schema changes:
migrations/
โโ 20260101120000_init/
โโ migration.sql
Your database is now in sync with your schema.
โ Generated Prisma Client (v7.x.x) to ./node_modules/@prisma/client in 48ms
--name init names the migration, migrations are numbered by timestamp, so the exact folder name reflects when it ran. Notice the last line, prisma migrate dev calls prisma generate automatically, no separate step needed.
Project File Structure After Migrating
prisma-intro/
โโโ package.json
โโโ .env
โโโ prisma/
โ โโโ schema.prisma
โ โโโ migrations/
โ โโโ 20260101120000_init/
โ โโโ migration.sql
โโโ node_modules/
Reading the Generated Migration File
-- migrations/20260101120000_init/migration.sql
-- CreateTable
CREATE TABLE "Student" (
"id" SERIAL NOT NULL,
"name" TEXT NOT NULL,
"grade" INTEGER NOT NULL,
"email" TEXT,
"enrolledAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "Student_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "Student_email_key" ON "Student"("email");
This is real, plain SQL, CREATE TABLE, PRIMARY KEY, CREATE UNIQUE INDEX, generated directly from the schema in the last lesson. SERIAL for the auto-incrementing ID, TIMESTAMP(3) DEFAULT CURRENT_TIMESTAMP for @default(now()), a nullable TEXT for the optional email field, everything traceable straight back to Module 1’s raw SQL.
Confirming It Worked
Connect with psql (Module 1, Lesson 2) and check:
\dt
List of relations
Schema | Name | Type | Owner
--------+------------+-------+----------
public | Student | table | postgres
The table exists in the real database now, created entirely from the schema file, no hand-written CREATE TABLE needed.
Migration History Is Tracked
Prisma also creates a _prisma_migrations table in the database itself, recording which migrations have already been applied. Running prisma migrate dev again with no schema changes does nothing, it already knows this migration ran.
Try It
- Run
prisma migrate dev --name initagainst your ownStudentmodel from the last lesson. - Open the generated
migration.sqlfile, and match every line back to a field or attribute in your schema. - Connect with
psql, and confirm the table exists with\dtand\d "Student". - Run
prisma migrate deva second time with no schema changes, and observe that it reports nothing to do.
Recap
prisma migrate dev --name <name>generates a SQL migration file from schema changes, applies it to the database, and regenerates Prisma Client, all in one command.- Migration files are plain, readable SQL, stored under
prisma/migrations/, and can be reviewed like any other generated code. - Prisma tracks which migrations have already run in a
_prisma_migrationstable, so re-running the command is always safe.
Next lesson: changing a model and running a second migration, evolving a schema that’s already been applied.