Welcome, everyone! Today, we’re diving into an essential part of building real-world applications: seeding data and handling migrations using Prisma.js. Why does this matter? Imagine you’re creating a blogging app—like the one we’ll build together in this lecture. You want users to see posts right away and admins to manage them, but how do you set up that initial data? And what happens when your app evolves, and you need to change your database structure? That’s where seeding and migrations come in—they’re the backbone of keeping your database organised and functional.
Let’s start with a relatable problem: You’ve built a blog app, but when you launch it, the homepage is empty—no posts! Users leave, frustrated. Seeding fixes that by populating your database with starter data. Now, imagine you decide to add a "tags" feature to filter posts, but your database doesn’t know about tags yet. Migrations let you update your database safely without breaking everything. These tools are critical for any developer, and mastering them will save you headaches down the road.
Here’s a common misconception: “I’ll just manually add data or tweak my database when I need to.” That works for a tiny project, but in the real world, you need a systematic approach with teams and live users. Let’s bust another myth: “Migrations are scary and might delete my data.” Done right, they’re safe and powerful. Ready to see how? Let’s jump in with our blog app example.
First, let’s tackle migrations. Prisma is an ORM (Object-Relational Mapping) tool that makes working with databases easier. Migrations are how Prisma keeps your database schema in sync with your code. Think of it like updating the blueprint of a house as you add new rooms. Prisma offers two main commands for this: migrate
and db push
. Let’s explore both using our blog app.
We will start with a simple schema in schema.prisma
:
model Post {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
}
To apply this to your database, you have two options:
<strong>prisma migrate dev</strong>
Run:
pnpx prisma migrate dev --name init
This creates a migration file (e.g., 20250310120000_init
) in a migrations
folder. It’s a SQL script that tells your database (like PostgreSQL) to create the Post
table. Prisma tracks every change in these files, building a history of your database’s evolution. Now, let’s add tags:
model Post {
id Int @id @default(autoincrement())
title String
content String
createdAt DateTime @default(now())
tags String[]
}
Run:
npx prisma migrate dev --name add_tags
Prisma compares the old schema to the new one, generates a new migration file to add the tags
column, and applies it. Existing posts get an empty tags array by default. This is great for production apps because it’s controlled, repeatable, and reversible.
<strong>prisma db push</strong>
Instead, you could run:
npx prisma db push
This directly updates your database to match the schema—no migration files, no history. After adding tags
, run it again, and the database instantly reflects the change. It’s faster but less structured.
<strong>migrate</strong>
vs. <strong>db push</strong>
migrate dev
creates a versioned history (migration files); db push
doesn’t—it just syncs the database.migrate dev
for team projects or production where you need control and rollback options. Use db push
for quick prototyping or solo development.migrate
lets you review and tweak changes before applying; db push
applies them immediately, which can risk data loss if you’re not careful (e.g., dropping a column deletes its data).migrate dev
ensures we can share schema changes with a team and deploy safely. With db push
, we’d skip the paperwork but lose the ability to undo mistakes.Post
model and run npx prisma migrate dev --name init
. The table is created with a migration file.tags
.npx prisma migrate dev --name add_tags
. A new migration file adds the column, and we’re ready for production.npx prisma db push
. The database updates instantly—no files, no fuss, but no history either.tags
column, but migrate
gives us structure, while push
gives us speed.Now, let’s populate our blog with data using seeding. Seeding means preloading your database with initial data—like sample posts—so it’s not empty when users arrive.
Create a seed.ts
file in the prisma
folder:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
await prisma.post.createMany({
data: [
{ title: "My First Post", content: "Hello, world!", tags: ["intro"] },
{ title: "Tech Trends", content: "AI is the future.", tags: ["tech"] },
],
});
}
main()
.then(() => prisma.$disconnect())
.catch((e) => {
console.error(e);
prisma.$disconnect();
process.exit(1);
});
Update package.json
:
"scripts": {
"prisma:seed": "ts-node prisma/seed.ts"
}
After migrating (or pushing), run:
npm run prisma:seed
Your database now has two posts, ready for users to filter by tags.
For a more complex seed, add dates and more tags:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
await prisma.post.deleteMany(); // Clear existing data (optional)
await prisma.post.createMany({
data: [
{
title: "My First Post",
content: "Hello, world!",
createdAt: new Date("2025-01-01"),
tags: ["intro", "welcome"],
},
{
title: "Tech Trends",
content: "AI is the future.",
createdAt: new Date("2025-02-15"),
tags: ["tech", "innovation"],
},
{
title: "Travel Diary",
content: "Exploring the mountains.",
createdAt: new Date("2025-03-01"),
tags: ["travel", "nature"],
},
],
});
console.log("Database seeded!");
}
main()
.then(() => prisma.$disconnect())
.catch((e) => {
console.error(e);
prisma.$disconnect();
process.exit(1);
});
Run it again, and your app has a rich starting point.
Our blog app ties migrations and seeding together:
/posts?tag=travel
).schema.prisma
.migrate dev
for production or db push
for prototyping to apply changes.npm run prisma:seed
.const posts = await prisma.post.findMany({
where: { tags: { has: "tech" } },
});
“Won’t db push
break my app?” Only if you’re reckless—test first! “Seeding feels fake!” It’s a standard way to mimic real usage.
Today, we mastered Prisma migrations and seeding for our blog app.
migrate dev
for structure, db push
for speed, seeding for instant data.migrate
in teams, test db push
carefully, keep seeds realistic.migrate
and push
carelessly, avoid untested schema changes.You’re ready to build a dynamic blog app. Next, we’ll query this data to power your features. Great job—keep exploring Prisma!
Let's explore how you can populate empty databases with data and how to handle changes in your database using migrations safely!
Why does seeding and migration matter and why are they important?
Imagine launching your blog app—users visit, see no posts, and leave. Seeding data fixes this by adding starter posts fast.
Adding tags to posts sounds simple, but your database won’t keep up without migrations. Prisma makes it smooth.
You may think that you can tweak databases by hand. With large teams and multiple stakeholders, that’s a mess and a recipe for ultimate disaster—Prisma’s tools are the pro way.
Let's take a look how you can safely maintain your database structure.,
With Prisma, you keep your database structure in a Prisma schema. This is an example of a simple schema describing the Post table.
Now imagine, you want to add a new column "tags" to the Post table. The tags field lets users filter posts and migrations make it happen without chaos.
Run pnpx prisma migrate dev to create migration files. It’s like saving your database’s history—perfect for teams.
You can also run pnpx prisma db push, which updates your DB directly without keeping a record of changes. Great for quick prototypes when working solo, but rollback is impossible (e.g. you cannot undo your changes).
Let's check out how migration differs from db push!
With migration, your changes start by modifying your Prisma schema.
When you run migration, migration files are produced, migration is applied to the database and a new client is generated.
These migration files can be used to roll back the changes or added to the repository for developers to apply them in the local context.
With push, the changes are instantaneous, and no record of the modification is retained. This allows you to swiftly iterate over updates in your database, but it can pose challenges for sharing those changes with your team, especially if they are using local versions of the same database. Therefore, while this method is excellent for solo work when you need to rapidly implement changes, the lack of a rollback option and the inability to track your updates makes it a somewhat risky approach!
Seeding adds posts so your app feels alive from day one. No one likes an empty blog!
Admins can seed realistic data to test features like filtering by tag or date.
Check out this script, which seeds two posts. You can run it with pnpx tsx prisma/seed.ts after migrations to populate your DB.
Clearing data ensures a clean slate—key for consistent testing.
These posts give users something to see and filter, solving the empty-app problem.
Let's wrap this up!
The key takeaways to remember is that you should use migrations for structure and control and db push for speed. You also can use seeding for instant data
Use migrations in teams, and is you use db push, test it carefully. Also, if you use seeds, do not just use dummy data, keep seeds realistic.
Last, don’t mix migrate and push carelessly and avoid untested schema changes.