{"course":{"slug":"building-apps-with-ai","title":"Building Apps with AI","url":"https://addaly.com/learn/building-apps-with-ai","level":"beginner","track":"build"},"lesson":{"slug":"the-schema-and-the-migration","title":"The schema, and the migration that changes it","url":"https://addaly.com/learn/building-apps-with-ai/the-schema-and-the-migration","position":49,"of":87,"minutes":9,"answer":"A migration is a numbered, forward-only file that changes the tables, and the one command that skips migrations by resetting the database is the one that deletes everybody's data.","keyIdea":"A migration is a numbered, forward-only file that changes the tables, and the one command that skips migrations by resetting the database is the one that deletes everybody's data."},"body":{"format":"markdown","text":"## A table is a promise\n\nA schema says what a table holds: a `bookings` table has an `id`, a `user_id`, a `starts_at` timestamp and a `status` text. Every row keeps that shape, and every piece of code that reads or writes the table relies on it. Change the shape and the code that relied on the old one breaks, which is why changing it is a formal act with a name.\n\nIn the tools this course assumes (Prisma, Drizzle, or a framework's built-in ORM) the schema is a file in your repository. `schema.prisma` or `schema.ts` describes every table in code, and the database is made to match it. That file is the most important one in the project after the rules file, and the data-model lesson in the specification block is where its contents come from.\n\n## What a migration is\n\nA migration is a file containing the SQL that moves the database from one shape to the next:\n\n```sql\n-- 0007_add_cancelled_at.sql\nALTER TABLE bookings ADD COLUMN cancelled_at TIMESTAMP;\n```\n\nMigrations are numbered, applied in order, and never edited once applied. The database keeps a ledger table (`_prisma_migrations`, `__drizzle_migrations`) recording which have run, so running the migration command twice does nothing the second time. That ledger is why a fresh database and a year-old one can both be brought to the same shape by the same command: each applies whatever it has not yet seen.\n\nThe ORM writes the file for you. Edit the schema, then:\n\n```bash\nnpx prisma migrate dev --name add_cancelled_at    # Prisma: generate and apply locally\nnpx drizzle-kit generate && npx drizzle-kit migrate   # Drizzle: the same in two steps\n```\n\nRead the generated SQL before applying it. Most of the time it is three lines and obviously right. Occasionally it is a `DROP COLUMN` you did not intend, because you renamed a field in the schema and the tool saw a deletion and an addition: the same guessing problem git has with renames. A rename in production is done in two migrations: add the new column and copy the data, then drop the old one in a later release once nothing reads it.\n\n## Production is a deliberate step\n\nLocally, `migrate dev` applies changes as you go. Production never runs that command. It runs the apply-only version (`prisma migrate deploy`, `drizzle-kit migrate`) as a step in the deployment, before the new code starts serving. Most hosts let you set this as a build command or a release command.\n\nThe failure mode from the earlier deploy lesson lives here: new code deployed, migration not run, and confident errors about a column that does not exist. Put the migration command in the deployment, not in your memory.\n\nAnd never change the production database by hand in a table editor to \"fix it quickly\". The ledger does not know, the next migration assumes a shape that is not there, and you have made the one kind of drift that is hard to diagnose.\n\n## The command that deletes everything\n\nEvery ORM has a reset: `prisma migrate reset`, `drizzle-kit push` with a force flag, `db:reset`. It drops every table and rebuilds from the migrations. On a local database with seed data it is convenient and harmless, and models learn it as the fix for any migration trouble, because in their training data it was, on somebody's laptop.\n\nAgainst production it is total data loss, and there is no confirmation beyond a yes-or-no prompt that an agent will answer. The protections, in order of strength: the agent never holds the production connection string; the reset commands are in the forbidden list in the rules file; and a backup that you have restored at least once exists before the first real user signs up. All three, because each fails on its own.\n\n## `db push` is not a migration\n\nPrisma's `db push` and Drizzle's `push` skip the migration file and alter the database directly to match the schema. They are quick for prototyping and they leave no record, so a second environment cannot be brought to the same shape. Fine for the first week on a laptop. Once anything is deployed, switch to migrations and do not mix the two: a database pushed to one shape and then migrated from another produces exactly the \"column already exists\" error from the check below.\n\n## Reading the ledger\n\nWhen a migration fails halfway, the ledger shows it as started and not finished, and the tool refuses to continue. The fix is to look at what actually got applied (is the column there or not) and either mark the migration as applied or roll it back, using the tool's own commands for that (`prisma migrate resolve` is the Prisma one). It is a five-minute problem when you look, and a reset when you do not."},"check":{"question":"A migration fails against the production database with 'column already exists'. The agent proposes running the ORM's reset command 'to get the database into a clean state'. What will that do?","options":[{"text":"Repair the ledger of applied migrations so the failed one can be retried cleanly against the existing tables.","correct":false,"tempting":"That is what people hope the word reset means; the command's actual definition is to drop every table and rebuild from zero."},{"text":"Drop every table, including the data in them, and rebuild the schema from scratch.","correct":true},{"text":"Roll back only the failed migration, leaving the data untouched.","correct":false,"tempting":"Rollback of a single migration is a different, rarely automated operation; reset is total by design."},{"text":"Nothing harmful, because production databases refuse destructive commands.","correct":false,"tempting":"The database executes whatever the connection is permitted to run; there is no built-in notion of 'production' that refuses."}]},"prev":{"slug":"where-the-data-goes-when-the-app-restarts","title":"Where the data goes when the app restarts","url":"https://addaly.com/learn/building-apps-with-ai/where-the-data-goes-when-the-app-restarts"},"next":{"slug":"the-columns-every-table-needs","title":"The columns every table needs","url":"https://addaly.com/learn/building-apps-with-ai/the-columns-every-table-needs"},"authorship":"Founder and series editor: Dr Mustafa Mun. Written by AI models to a written standard. Exam keys checked blind by a second model: 3,665 of 3,666 agreed. Academic review invited.","review":{"status":"Draft, open for academic review"},"cite":"Addaly (2026) 'The schema, and the migration that changes it', in Building Apps with AI. Edited by M. Mun. Addaly. Available at: https://addaly.com/learn/building-apps-with-ai/the-schema-and-the-migration (Accessed: 13 October 2026).","licence":{"name":"CC BY-NC-SA 4.0","url":"https://creativecommons.org/licenses/by-nc-sa/4.0/"},"reader":{"schema":1,"title":"The schema, and the migration that changes it","courseTitle":"Building Apps with AI","module":{"index":6,"slug":"the-parts-that-persist","title":"The parts that persist: data, accounts and files"},"answer":null,"keyIdea":"A migration is a numbered, forward-only file that changes the tables, and the one command that skips migrations by resetting the database is the one that deletes everybody’s data.","hero":{"kind":"plate","image":null,"alt":null,"caption":null},"blocks":[{"t":"h2","id":"a-table-is-a-promise","runs":[{"text":"A table is a promise"}]},{"t":"p","runs":[{"text":"A schema says what a table holds: a "},{"text":"bookings","code":true},{"text":" table has an "},{"text":"id","code":true},{"text":", a "},{"text":"user_id","code":true},{"text":", a "},{"text":"starts_at","code":true},{"text":" timestamp and a "},{"text":"status","code":true},{"text":" text. Every row keeps that shape, and every piece of code that reads or writes the table relies on it. Change the shape and the code that relied on the old one breaks, which is why changing it is a formal act with a name."}]},{"t":"p","runs":[{"text":"In the tools this course assumes (Prisma, Drizzle, or a framework’s built-in ORM) the schema is a file in your repository. "},{"text":"schema.prisma","code":true},{"text":" or "},{"text":"schema.ts","code":true},{"text":" describes every table in code, and the database is made to match it. That file is the most important one in the project after the rules file, and the data-model lesson in the specification block is where its contents come from."}]},{"t":"h2","id":"what-a-migration-is","runs":[{"text":"What a migration is"}]},{"t":"p","runs":[{"text":"A migration is a file containing the SQL that moves the database from one shape to the next:"}]},{"t":"code","lang":"sql","label":"SQL","text":"-- 0007_add_cancelled_at.sql\nALTER TABLE bookings ADD COLUMN cancelled_at TIMESTAMP;"},{"t":"p","runs":[{"text":"Migrations are numbered, applied in order, and never edited once applied. The database keeps a ledger table ("},{"text":"_prisma_migrations","code":true},{"text":", "},{"text":"__drizzle_migrations","code":true},{"text":") recording which have run, so running the migration command twice does nothing the second time. That ledger is why a fresh database and a year-old one can both be brought to the same shape by the same command: each applies whatever it has not yet seen."}]},{"t":"p","runs":[{"text":"The ORM writes the file for you. Edit the schema, then:"}]},{"t":"code","lang":"bash","label":"Bash","text":"npx prisma migrate dev --name add_cancelled_at    # Prisma: generate and apply locally\nnpx drizzle-kit generate && npx drizzle-kit migrate   # Drizzle: the same in two steps"},{"t":"p","runs":[{"text":"Read the generated SQL before applying it. Most of the time it is three lines and obviously right. Occasionally it is a "},{"text":"DROP COLUMN","code":true},{"text":" you did not intend, because you renamed a field in the schema and the tool saw a deletion and an addition: the same guessing problem git has with renames. A rename in production is done in two migrations: add the new column and copy the data, then drop the old one in a later release once nothing reads it."}]},{"t":"h2","id":"production-is-a-deliberate-step","runs":[{"text":"Production is a deliberate step"}]},{"t":"p","runs":[{"text":"Locally, "},{"text":"migrate dev","code":true},{"text":" applies changes as you go. Production never runs that command. It runs the apply-only version ("},{"text":"prisma migrate deploy","code":true},{"text":", "},{"text":"drizzle-kit migrate","code":true},{"text":") as a step in the deployment, before the new code starts serving. Most hosts let you set this as a build command or a release command."}]},{"t":"p","runs":[{"text":"The failure mode from the earlier deploy lesson lives here: new code deployed, migration not run, and confident errors about a column that does not exist. Put the migration command in the deployment, not in your memory."}]},{"t":"p","runs":[{"text":"And never change the production database by hand in a table editor to “fix it quickly”. The ledger does not know, the next migration assumes a shape that is not there, and you have made the one kind of drift that is hard to diagnose."}]},{"t":"h2","id":"the-command-that-deletes-everything","runs":[{"text":"The command that deletes everything"}]},{"t":"p","runs":[{"text":"Every ORM has a reset: "},{"text":"prisma migrate reset","code":true},{"text":", "},{"text":"drizzle-kit push","code":true},{"text":" with a force flag, "},{"text":"db:reset","code":true},{"text":". It drops every table and rebuilds from the migrations. On a local database with seed data it is convenient and harmless, and models learn it as the fix for any migration trouble, because in their training data it was, on somebody’s laptop."}]},{"t":"p","runs":[{"text":"Against production it is total data loss, and there is no confirmation beyond a yes-or-no prompt that an agent will answer. The protections, in order of strength: the agent never holds the production connection string; the reset commands are in the forbidden list in the rules file; and a backup that you have restored at least once exists before the first real user signs up. All three, because each fails on its own."}]},{"t":"h2","id":"db-push-is-not-a-migration","runs":[{"text":"db push","code":true},{"text":" is not a migration"}]},{"t":"p","runs":[{"text":"Prisma’s "},{"text":"db push","code":true},{"text":" and Drizzle’s "},{"text":"push","code":true},{"text":" skip the migration file and alter the database directly to match the schema. They are quick for prototyping and they leave no record, so a second environment cannot be brought to the same shape. Fine for the first week on a laptop. Once anything is deployed, switch to migrations and do not mix the two: a database pushed to one shape and then migrated from another produces exactly the “column already exists” error from the check below."}]},{"t":"h2","id":"reading-the-ledger","runs":[{"text":"Reading the ledger"}]},{"t":"p","runs":[{"text":"When a migration fails halfway, the ledger shows it as started and not finished, and the tool refuses to continue. The fix is to look at what actually got applied (is the column there or not) and either mark the migration as applied or roll it back, using the tool’s own commands for that ("},{"text":"prisma migrate resolve","code":true},{"text":" is the Prisma one). It is a five-minute problem when you look, and a reset when you do not."}]}],"check":{"question":[{"text":"A migration fails against the production database with ‘column already exists’. The agent proposes running the ORM’s reset command ‘to get the database into a clean state’. What will that do?"}],"options":[{"text":[{"text":"Repair the ledger of applied migrations so the failed one can be retried cleanly against the existing tables."}],"correct":false,"tempting":[{"text":"That is what people hope the word reset means; the command’s actual definition is to drop every table and rebuild from zero."}]},{"text":[{"text":"Drop every table, including the data in them, and rebuild the schema from scratch."}],"correct":true,"tempting":null},{"text":[{"text":"Roll back only the failed migration, leaving the data untouched."}],"correct":false,"tempting":[{"text":"Rollback of a single migration is a different, rarely automated operation; reset is total by design."}]},{"text":[{"text":"Nothing harmful, because production databases refuse destructive commands."}],"correct":false,"tempting":[{"text":"The database executes whatever the connection is permitted to run; there is no built-in notion of ‘production’ that refuses."}]}],"order":[2,1,3,0],"why":null},"prev":{"slug":"where-the-data-goes-when-the-app-restarts","title":"Where the data goes when the app restarts","minutes":8},"next":{"slug":"the-columns-every-table-needs","title":"The columns every table needs","minutes":7}}}