This guide covers database seeding using drizzle-seed for applications deployed to ephemeral PR environments.
- Testing: Provide realistic data for QA and manual testing
- Development: Start with meaningful data instead of empty tables
- Demos: Pre-populate environments for stakeholder reviews
- Deterministic: Same seed produces same data across environments
pnpm add -D drizzle-seedCreate src/db/seed.ts:
import { drizzle } from 'drizzle-orm/node-postgres';
import { seed } from 'drizzle-seed';
import { Pool } from 'pg';
import * as schema from './schema';
async function main() {
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const db = drizzle(pool, { schema });
await seed(db, schema);
console.log('Seeding completed');
await pool.end();
}
main();{
"scripts": {
"db:seed": "tsx src/db/seed.ts"
}
}pnpm db:seeddrizzle-seed provides many built-in generators for realistic data:
await seed(db, schema).refine((funcs) => ({
users: {
count: 100,
columns: {
firstName: funcs.firstName(),
lastName: funcs.lastName(),
fullName: funcs.fullName(),
email: funcs.email(),
phone: funcs.phoneNumber({ template: '(###) ###-####' }),
},
},
}));await seed(db, schema).refine((funcs) => ({
companies: {
count: 50,
columns: {
name: funcs.companyName(),
address: funcs.streetAddress(),
city: funcs.city(),
state: funcs.state(),
country: funcs.country(),
postalCode: funcs.postcode(),
},
},
}));await seed(db, schema).refine((funcs) => ({
employees: {
count: 200,
columns: {
title: funcs.jobTitle(),
hireDate: funcs.date({ minDate: '2020-01-01', maxDate: '2024-12-31' }),
},
},
}));await seed(db, schema).refine((funcs) => ({
posts: {
count: 500,
columns: {
title: funcs.loremIpsum({ sentenceCount: 1 }),
content: funcs.loremIpsum({ sentenceCount: 10 }),
},
},
}));const statuses = ['active', 'pending', 'inactive', 'banned'];
await seed(db, schema).refine((funcs) => ({
users: {
count: 100,
columns: {
status: funcs.valuesFromArray({ values: statuses }),
},
},
}));await seed(db, schema).refine((funcs) => ({
orders: {
count: 1000,
columns: {
status: funcs.weightedRandom([
{ weight: 0.7, value: funcs.default({ defaultValue: 'completed' }) },
{ weight: 0.2, value: funcs.default({ defaultValue: 'processing' }) },
{ weight: 0.1, value: funcs.default({ defaultValue: 'cancelled' }) },
]),
},
},
}));await seed(db, schema).refine((funcs) => ({
products: {
count: 200,
columns: {
price: funcs.number({ minValue: 9.99, maxValue: 999.99, precision: 100 }),
quantity: funcs.int({ minValue: 0, maxValue: 1000 }),
},
},
}));import { count } from 'drizzle-orm';
import { users } from './schema';
async function seedIfEmpty(db) {
const result = await db.select({ count: count() }).from(users);
if (result[0].count > 0) {
console.log('Database already has data, skipping seed');
return false;
}
await seed(db, schema);
return true;
}// In your database service initialization
async onModuleInit() {
// Run migrations first
await migrate(this.db, { migrationsFolder: './drizzle' });
// Then seed if empty
const result = await this.db.select({ count: count() }).from(users);
if (result[0].count === 0) {
await seedDatabase(this.pool);
console.log('Database seeded with initial data');
}
}For reproducible data across environments, use versioned seeding:
await seed(db, schema, { version: '1' });The same version always produces the same data, which is useful for:
- Consistent test data across CI runs
- Reproducible bug reports
- Stable demo environments
Clear all data before re-seeding:
import { reset } from 'drizzle-seed';
async function resetAndSeed(db) {
// Clear all tables
await reset(db, schema);
// Re-seed
await seed(db, schema);
}Warning: reset deletes all data. Use with caution.
Drizzle-seed supports seeding related tables:
await seed(db, schema).refine((funcs) => ({
users: {
count: 10,
},
posts: {
count: 100,
// Each post will be assigned to an existing user
with: {
users: { weight: 1 },
},
},
}));if (process.env.NODE_ENV !== 'production') {
await seedDatabase(pool);
}Use built-in generators that produce realistic data rather than "test1", "test2", etc.
// Increment version when seed logic changes
await seed(db, schema, { version: '2' });/**
* Seed data for demo environments:
* - 10 users (various roles)
* - 100 posts (assigned to users)
* - 500 comments (on posts)
*/
async function seedDemoData(db) {
// ...
}try {
await seedDatabase(pool);
} catch (error) {
console.error('Seeding failed:', error);
// Don't crash the app - seeding is nice-to-have
}- name: Seed database
env:
DATABASE_URL: ${{ secrets.DATABASE_URL }}
run: pnpm db:seedFor ephemeral PR environments, seed automatically at app startup:
// In DatabaseService
async onModuleInit() {
await this.runMigrations();
await this.seedIfEmpty();
}- Check migrations ran first (tables must exist)
- Verify DATABASE_URL is correct
- Check for errors in seed script output
- Add check for existing data before seeding
- Use
reset()if you need to re-seed
- Ensure schema types match seed values
- Update drizzle-seed to latest version