CodingNic

Defining Models and Migrations

Evolving the Schema

Defining Models and Migrations 10 min read

Evolving the Schema

Objectives

By the end of this lesson, you should be able to:

  • Add a field to an existing model and migrate the change
  • Read a migration file that alters, rather than creates, a table
  • Explain what happens to existing rows when a new required field is added

💡 Why this matters: Real schemas change constantly, a new field, a renamed column, a new table. This lesson covers the normal, everyday case: changing a model that’s already been migrated once.

⚠️ A note on verification: as throughout this module, the Prisma CLI can’t run inside this course’s own sandboxed tooling. The commands and output below reflect Prisma’s stable, current, documented behavior. Try it yourself against the project from the last two lessons.

Adding a Field

Add phone to the Student model from the last lesson:

text
model Student {
  id         Int      @id @default(autoincrement())
  name       String
  grade      Int
  email      String?  @unique
  phone      String?
  enrolledAt DateTime @default(now())
}

Migrating the Change

bash
npx prisma migrate dev --name add_phone
text
Applying migration `20260101130000_add_phone`

The following migration(s) have been created and applied from new schema changes:

migrations/
  └─ 20260101130000_add_phone/
    └─ migration.sql

Your database is now in sync with your schema.

✔ Generated Prisma Client (v7.x.x) to ./node_modules/@prisma/client in 41ms
sql
-- migrations/20260101130000_add_phone/migration.sql
-- AlterTable
ALTER TABLE "Student" ADD COLUMN "phone" TEXT;

A second, separate migration file, this one an ALTER TABLE, not a CREATE TABLE. Prisma compares the current schema to the last applied migration, and generates only the SQL needed for the difference.

Required Fields on an Existing Table

Adding an optional field (phone String?) is safe, existing rows simply get NULL for it. Adding a required field to a table that already has rows is trickier, PostgreSQL needs a value for every existing row too:

text
model Student {
  id       Int    @id @default(autoincrement())
  name     String
  grade    Int
  homeroom String
}
bash
npx prisma migrate dev --name add_homeroom
text
? We need to reset the "school" database at localhost:5432

You may lose all data in this database.
✔ Do you want to continue? All data will be lost. … yes

If the table already has rows and no default is given, Prisma warns that it can’t safely add a required column without one, and offers to reset the database in development. For an existing table with real data, the safer path is to add the field as optional first, backfill a value for every row, then follow up with a second migration making it required, or provide a @default(...) value so existing rows have something to fall back on:

text
model Student {
  id       Int    @id @default(autoincrement())
  name     String
  grade    Int
  homeroom String @default("Unassigned")
}

With a default, the migration succeeds without any data loss, existing rows get "Unassigned", new rows can still specify a real value.

Try It

  1. Add an optional field to your own Student model, and migrate it with prisma migrate dev --name <something descriptive>.
  2. Read the generated migration file, and confirm it’s an ALTER TABLE, not a CREATE TABLE.
  3. Add a required field with a @default(...) value, migrate it, and confirm existing rows received the default.
  4. Explain, in your own words, why adding a required field with no default is risky on a table that already has data.

Recap

  • Adding a field and running prisma migrate dev again generates an ALTER TABLE migration, only the difference, not the whole schema.
  • Optional fields are always safe to add to an existing table, required fields need a @default(...) or a backfill plan.
  • Every migration is its own file, in order, forming a complete, readable history of how the schema has changed over time.

This is the final lesson of this module before exercises. Next module: modeling relationships between models, one-to-many and many-to-many.