Seeders

Populate your database with initial data using typed seeder files.

Overview

Seeders are TypeScript files in database/seeders/ that insert initial or test data. Each seeder exports an async run() function. Seeders use the same model and service APIs as the rest of your application.

database/
└── seeders/
    ├── 01_user_seeder.ts
    └── 02_post_seeder.ts

make:seeder

Generate a new seeder:

tekir make:seeder user
Created: database/seeders/user_seeder.ts

Seeder File Format

Export an async run function. Use models, services, or raw SQL, whatever you need.

database/seeders/01_user_seeder.ts
import { User } from '~/models/user'
import { hash } from '#services'

export async function run() {
  const pw = await hash.make('secret')

  await User.createMany([
    { name: 'Alice', email: '[email protected]', role: 'admin', password: pw },
    { name: 'Bob', email: '[email protected]', role: 'user', password: pw },
    { name: 'Charlie', email: '[email protected]', role: 'user', password: pw }
  ])
}
database/seeders/02_post_seeder.ts
import { Post } from '~/models/post'

export async function run() {
  await Post.createMany([
    { title: 'Getting Started with tekir', body: 'tekir is a Bun-native framework...', userId: 1, status: 'published' },
    { title: 'Building APIs', body: 'Learn how to build REST APIs...', userId: 1, status: 'published' },
    { title: 'Draft Post', body: 'This is still a draft...', userId: 2, status: 'draft' }
  ])
}

You can also use raw SQL for simple inserts:

database/seeders/03_settings_seeder.ts
import { db } from '#services'

export async function run() {
  await db.exec(`
    INSERT INTO settings (key, value) VALUES
    ('site_name', 'My App'),
    ('theme', 'dark'),
    ('locale', 'en')
  `)
}

Running Seeders

Run all seeders in alphabetical order:

# Run all seeders in alphabetical order
tekir seed
Seeded: 01_user_seeder.ts
  Seeded: 02_post_seeder.ts

Idempotent Seeders

Running seed multiple times inserts duplicate records by default. Guard against this by checking if data exists first:

import { User } from '~/models/user'
import { hash } from '#services'

export async function run() {
  // Skip if users already exist
  if ((await User.count()) > 0) return

  const pw = await hash.make('secret')
  await User.createMany([
    { name: 'Alice', email: '[email protected]', role: 'admin', password: pw },
    { name: 'Bob', email: '[email protected]', role: 'user', password: pw }
  ])
}

Or use firstOrCreate() for per-row idempotency:

import { User } from '~/models/user'
import { hash } from '#services'

export async function run() {
  const pw = await hash.make('secret')

  await User.firstOrCreate(
    { email: '[email protected]' },
    { name: 'Alice', role: 'admin', password: pw }
  )

  await User.firstOrCreate(
    { email: '[email protected]' },
    { name: 'Bob', role: 'user', password: pw }
  )
}

Seeder Order

Seeders run in alphabetical order. Prefix filenames with numbers to control execution order and satisfy foreign key dependencies:

database/seeders/
  01_user_seeder.ts     , runs first
  02_category_seeder.ts , runs second
  03_post_seeder.ts     , runs third (depends on users + categories)

Fresh + Seed

To wipe and re-seed your local database during development:

# Drop all tables, re-run migrations, then seed
tekir migrate:fresh
tekir seed
  • migrate:fresh: drops all tables and re-runs all migrations
  • seed: populates with seed data