Seeders & Factories
Seeders and factories give you a structured way to populate a local D1 database with realistic test data. Factories define how to generate a single model's attributes; seeders orchestrate how many records to create and in what order.
Overview
| Concept | Responsibility |
|---|---|
| Factory | Generates attribute objects for a single model; inserts rows via wrangler |
| Seeder | Uses factories to create records; controls count and relations |
| CLI seed | Loads and runs a seeder file by name against a D1 database |
Generate with the CLI
The CLI scaffolds both files for you:
# Generate a factory for the User model
npx @orphnet/d1-eloquent make:factory UserFactory
# Generate a seeder for the User model
npx @orphnet/d1-eloquent make:seeder UserSeederOr generate a model, migration, factory, and seeder all at once:
npx @orphnet/d1-eloquent make:resource UserGenerated files are placed in:
src/database/factories/UserFactory.tssrc/database/seeders/UserSeeder.ts
Writing a Factory
Extend Factory<TAttrs> from @orphnet/d1-eloquent. Define table and definition() — definition() returns one complete set of attributes:
// src/database/factories/UserFactory.ts
import { Factory } from '@orphnet/d1-eloquent'
export type TUserAttrs = {
id: string
name: string
email: string
role: string
created_at: string
updated_at: string
}
const NAMES = ['Alice', 'Bob', 'Carol', 'Dave', 'Eve']
export class UserFactory extends Factory<TUserAttrs> {
public readonly table = 'users'
public definition(): TUserAttrs {
const name = NAMES[Math.floor(Math.random() * NAMES.length)]
const now = new Date().toISOString()
return {
id: crypto.randomUUID(),
name,
email: `${name.toLowerCase()}.${Date.now()}@example.com`,
role: 'user',
created_at: now,
updated_at: now,
}
}
}Factory methods
| Method | Returns | Description |
|---|---|---|
factory.make(overrides?) | TAttrs | Build one attribute object in memory |
factory.makeMany(count, overrides?) | TAttrs[] | Build many attribute objects in memory |
factory.create(opts, overrides?) | Promise<TAttrs> | Build and insert one row |
factory.createMany(opts, count, overrides?) | Promise<TAttrs[]> | Build and insert many rows |
opts is the TSeederOpts passed to your seeder's run() function. Pass it through directly.
Writing a Seeder
A seeder is a TSeeder object with a name and a run(opts) function. The opts argument contains the database config — pass it to factory methods for insertion:
// src/database/seeders/UserSeeder.ts
import type { TSeeder, TSeederOpts } from '@orphnet/d1-eloquent/cli'
import { UserFactory } from '../factories/UserFactory'
const seeder: TSeeder = {
name: 'UserSeeder',
run: async (opts: TSeederOpts): Promise<void> => {
const factory = new UserFactory()
// Create 10 regular users
await factory.createMany(opts, 10)
// Create one admin with specific fields
await factory.create(opts, { role: 'admin', email: 'admin@example.com' })
console.log('Seeded 11 users')
},
}
export default seederSeeding Relational Data
When seeding models with foreign key relationships, seed parent models first:
// src/database/seeders/PostSeeder.ts
import type { TSeeder, TSeederOpts } from '@orphnet/d1-eloquent/cli'
import { UserFactory } from '../factories/UserFactory'
import { PostFactory } from '../factories/PostFactory'
const seeder: TSeeder = {
name: 'PostSeeder',
run: async (opts: TSeederOpts): Promise<void> => {
const users = new UserFactory()
const posts = new PostFactory()
// 1. Create authors first
const authors = await users.createMany(opts, 5)
// 2. Create 3 posts per author
for (const author of authors) {
await posts.createMany(opts, 3, { author_id: author.id })
}
console.log(`Seeded ${authors.length} authors and ${authors.length * 3} posts`)
},
}
export default seederSeed order matters
If your posts table has a foreign key on author_id referencing users.id, you must seed users before posts. Run seeders in the correct order by invoking them with --seeder separately, or in separate CLI calls.
Running Seeders
The CLI loads seeders by name from src/database/seeders/. Run a single seeder:
npx @orphnet/d1-eloquent seed --db DB --local --seeder UserSeederRun a default DatabaseSeeder (the name the CLI looks for when --seeder is omitted):
npx @orphnet/d1-eloquent seed --db DB --localRun against your remote Cloudflare D1 database:
npx @orphnet/d1-eloquent seed --db DB --remoteUse local seeding for development
Pair seed with fresh to start with a clean slate:
# Drop all tables, rerun all migrations, then seed
npx @orphnet/d1-eloquent fresh --db DB --local
npx @orphnet/d1-eloquent seed --db DB --local --seeder UserSeederFactory make vs create
Use make/makeMany when you only need attribute objects in memory (e.g., to pass into Model.create() yourself, or in unit tests). Use create/createMany when you want the factory to insert the rows directly via wrangler.
const factory = new UserFactory()
// In-memory only — no DB write
const attrs = factory.make({ role: 'admin' })
// Inserts into D1 via wrangler
await factory.create(opts, { role: 'admin' })