Complete Guide to Prisma Seed Data for Development
- backlinksindiit
- 7 days ago
- 5 min read
Empty databases waste development time. Clone a repo, run migrations, stare at empty tables wondering how to test anything. New team members spend hours creating test data manually. Database resets mean recreating everything from scratch. Prisma seed data solves this by automating data population for development and testing environments.
Project Setup and File Structure
Standard Prisma project structure includes dedicated seeding files:
project-root/
├── prisma/
│ ├── schema.prisma
│ ├── seed.ts
│ └── migrations/
├── package.json
└── src/
The prisma/seed.ts file contains seeding logic. Configure package.json to execute it:
{
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
"devDependencies": {
"@types/node": "^20.11.5",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
}
}
Run seeding with npx prisma db seed. Prisma Migrate automatically triggers seeding during npx prisma migrate reset and when creating new development databases.
Building Idempotent Seed Scripts with Upsert
Seed scripts must run multiple times without errors. Using create() fails on second execution when records already exist. The upsert() method creates new records or updates existing ones based on unique constraints:
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
const admin = await prisma.user.upsert({
where: { email: 'admin@example.com' },
update: {},
create: {
email: 'admin@example.com',
name: 'Admin User',
role: 'ADMIN',
},
});
const testUser = await prisma.user.upsert({
where: { email: 'test@example.com' },
update: { name: 'Test User Updated' },
create: {
email: 'test@example.com',
name: 'Test User',
role: 'USER',
posts: {
create: [
{
title: 'First Post',
content: 'Testing content creation',
published: true,
},
{
title: 'Draft Post',
content: 'Work in progress',
published: false,
},
],
},
},
});
console.log({ admin, testUser });
}
main()
.then(async () => {
await prisma.$disconnect();
})
.catch(async (error) => {
console.error(error);
await prisma.$disconnect();
process.exit(1);
});
First run creates users and posts. Subsequent runs find existing users by email, execute updates (or skip with empty update: {}), and avoid duplicate key errors. The nested posts.create syntax establishes relationships during initial creation.
Performance Benchmarks: Individual vs Batch Operations
Real-world performance testing shows dramatic differences between approaches:
Tested on PostgreSQL 15 with Prisma 5.x. Individual creates execute separate database round trips. Batch operations combine multiple inserts into fewer queries, reducing network overhead by 80-90%.
async function seedUsersBatch() {
const users = Array.from({ length: 1000 }).map((_, i) => ({
email: `user${i}@example.com`,
name: `User ${i}`,
role: 'USER',
}));
await prisma.user.createMany({
data: users,
skipDuplicates: true,
});
}
The skipDuplicates: true flag prevents errors when emails already exist. Database ignores duplicates silently instead of throwing constraint violations.
Generating Realistic Data with Faker
Manual test data becomes tedious at scale. Faker generates realistic names, emails, addresses, and timestamps:
import { PrismaClient } from '@prisma/client'; import { faker } from '@faker-js/faker'; const prisma = new PrismaClient({ log: [] }); // Disable logging for speed async function seedWithFaker() { // Create users in batches const users = Array.from({ length: 500 }).map(() => ({ email: faker.internet.email(), name: faker.person.fullName(), bio: faker.lorem.paragraph(), avatar: faker.image.avatar(), createdAt: faker.date.past({ years: 2 }), })); await prisma.user.createMany({ data: users, skipDuplicates: true, }); // Get created user IDs for relationships const createdUsers = await prisma.user.findMany({ select: { id: true }, }); // Create posts linking to random users const posts = Array.from({ length: 2000 }).map(() => ({ title: faker.lorem.sentence(), content: faker.lorem.paragraphs(3), published: faker.datatype.boolean(), userId: createdUsers
.id,
createdAt: faker.date.past({ years: 1 }),
}));
await prisma.post.createMany({
data: posts,
});
console.log(`Seeded ${users.length} users and ${posts.length} posts`);
}
Notice the separate relationship handling. Create parent records first, fetch their IDs, then create child records referencing parents. Nested creates work for small datasets but become slow with thousands of records.
Seeding from CSV Files and External Data
Production-like data often comes from CSV exports or API dumps. Import CSV data using papaparse:
import { PrismaClient } from '@prisma/client';
import * as fs from 'fs';
import Papa from 'papaparse';
const prisma = new PrismaClient();
async function seedFromCSV() {
const csvFile = fs.readFileSync('./data/users.csv', 'utf8');
const parsed = Papa.parse(csvFile, {
header: true,
skipEmptyLines: true,
dynamicTyping: true,
});
const users = parsed.data.map((row: any) => ({
email: row.email?.trim(),
name: row.name?.trim(),
role: row.role || 'USER',
})).filter(user => user.email && user.name);
await prisma.user.createMany({
data: users,
skipDuplicates: true,
});
console.log(`Imported ${users.length} users from CSV`);
}
Teams working with mobile app development houston projects often need realistic datasets matching production scale. CSV imports provide actual data distributions for load testing without exposing customer information.
Environment-Specific Seeding Strategies
Different environments need different data volumes. Production requires minimal seed data (admin users, system settings). Development needs large realistic datasets. Tests need predictable minimal data.
import { parseArgs } from 'node:util';
const options = {
environment: { type: 'string' },
count: { type: 'string' },
};
async function main() {
const {
values: { environment, count },
} = parseArgs({ options });
const userCount = count ? parseInt(count) : 50;
switch (environment) {
case 'production':
await seedProduction();
break;
case 'test':
await seedTest();
break;
case 'staging':
await seedStaging(userCount);
break;
default:
await seedDevelopment(userCount);
}
}
async function seedProduction() {
await prisma.user.upsert({
where: { email: 'admin@company.com' },
update: {},
create: {
email: 'admin@company.com',
name: 'System Admin',
role: 'ADMIN',
},
});
await prisma.settings.upsert({
where: { key: 'app_version' },
update: { value: '1.0.0' },
create: { key: 'app_version', value: '1.0.0' },
});
}
async function seedTest() {
// Minimal predictable data for tests
await prisma.user.create({
data: {
email: 'test@test.com',
name: 'Test User',
posts: {
create: [
{ title: 'Test Post 1', content: 'Content', published: true },
{ title: 'Test Post 2', content: 'Content', published: false },
],
},
},
});
}
async function seedDevelopment(count: number) {
// Large realistic dataset with Faker
const users = Array.from({ length: count }).map(() => ({
email: faker.internet.email(),
name: faker.person.fullName(),
}));
await prisma.user.createMany({ data: users, skipDuplicates: true });
}
Execute with environment flags:
npx prisma db seed -- --environment production
npx prisma db seed -- --environment development --count 1000
Docker and CI/CD Integration
Seed scripts must work in containerized environments. Add seeding to Docker Compose:
version: '3.8'
services:
postgres:
image: postgres:15
environment:
POSTGRES_PASSWORD: password
POSTGRES_DB: myapp
ports:
- "5432:5432"
app:
build: .
depends_on:
- postgres
environment:
DATABASE_URL: postgresql://postgres:password@postgres:5432/myapp
command: >
sh -c "npx prisma migrate deploy &&
npx prisma db seed &&
npm start"
GitHub Actions workflow for testing with seeded data:
name: Test with Seeded Database on:
jobs:
test:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_PASSWORD: password
ports:
- 5432:5432
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '20'
- run: npm ci
- run: npx prisma migrate deploy
- run: npx prisma db seed -- --environment test
- run: npm test
Troubleshooting Common Seeding Issues
Issue: "Error: Cannot find module './prisma/seed'"
Solution: Check package.json has correct seed command and TypeScript dependencies installed. Verify file exists at prisma/seed.ts (not seed.js if using TypeScript).
Issue: Seeding hangs indefinitely
Solution: Missing await prisma.$disconnect() in finally block. Always disconnect Prisma Client:
main()
.then(async () => await prisma.$disconnect())
.catch(async (e) => {
console.error(e);
await prisma.$disconnect();
process.exit(1);
});
Issue: Foreign key constraint failures
Solution: Create parent records before children. Fetch parent IDs then create children referencing those IDs. Cannot use createMany() with nested creates.
Issue: Out of memory with large datasets
Solution: Process in batches. Instead of creating 100,000 records at once, create 1,000 at a time in loops.
Implementation Checklist:
Configure package.json with seed command and required dependencies
Create prisma/seed.ts with proper Prisma Client initialization
Use upsert() instead of create() for idempotent operations
Install @faker-js/faker for realistic test data generation
Implement createMany() for batch operations exceeding 100 records
Disable Prisma logging during seeding with new PrismaClient({ log: [] })
Handle relations by creating parents first, fetching IDs, then creating children
Add environment-specific seeding logic using command-line arguments
Include CSV import functionality for production-like datasets
Always call prisma.$disconnect() in both success and error paths
Add seeding to Docker Compose and CI/CD workflows
Test seed scripts run successfully multiple times without errors
Prisma seed data automation transforms development workflows. New developers onboard in minutes instead of hours. Database resets complete instantly. Testing with realistic data becomes standard practice. The hour spent building proper seed scripts saves weeks of cumulative time across the team.
Comments