Skip to content

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

ConceptResponsibility
FactoryGenerates attribute objects for a single model; inserts rows via wrangler
SeederUses factories to create records; controls count and relations
CLI seedLoads and runs a seeder file by name against a D1 database

Generate with the CLI

The CLI scaffolds both files for you:

sh
# 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 UserSeeder

Or generate a model, migration, factory, and seeder all at once:

sh
npx @orphnet/d1-eloquent make:resource User

Generated files are placed in:

  • src/database/factories/UserFactory.ts
  • src/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:

ts
// 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

MethodReturnsDescription
factory.make(overrides?)TAttrsBuild 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:

ts
// 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 seeder

Seeding Relational Data

When seeding models with foreign key relationships, seed parent models first:

ts
// 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 seeder

Seed 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:

sh
npx @orphnet/d1-eloquent seed --db DB --local --seeder UserSeeder

Run a default DatabaseSeeder (the name the CLI looks for when --seeder is omitted):

sh
npx @orphnet/d1-eloquent seed --db DB --local

Run against your remote Cloudflare D1 database:

sh
npx @orphnet/d1-eloquent seed --db DB --remote

Use local seeding for development

Pair seed with fresh to start with a clean slate:

sh
# 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 UserSeeder

Factory 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.

ts
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' })

Released under the MIT License.