bknd-seed-data

Use when populating a Bknd database with initial or test data. Covers the seed function in options, ctx.em.mutator() for insertOne/insertMany, conditional seeding, environment-based data, and common patterns for dev/test fixtures.

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "bknd-seed-data" with this command: npx skills add cameronapak/bknd-skills/cameronapak-bknd-skills-bknd-seed-data

Seed Data

Populate your Bknd database with initial, test, or development data using the built-in seed function.

Prerequisites

  • Bknd project initialized
  • At least one entity defined
  • Code-first configuration (seed is code-only)

When to Use

  • Populating initial data on first startup
  • Creating test fixtures for development
  • Setting up demo data for presentations
  • Bootstrapping admin users or default records

Note: Seed function is code-only—no UI equivalent. For one-off data entry, use the admin panel Data section directly.

Code Approach

Step 1: Add Seed Function to Options

The seed function lives in the options section of your config:

import { type BunBkndConfig, serve } from "bknd/adapter/bun";
import { em, entity, text, boolean } from "bknd";

const schema = em({
  todos: entity("todos", {
    title: text().required(),
    done: boolean({ default_value: false }),
  }),
});

const config: BunBkndConfig = {
  connection: { url: "file:data.db" },
  config: {
    data: schema.toJSON(),
  },
  options: {
    seed: async (ctx) => {
      // Seed logic here
    },
  },
};

serve(config);

Step 2: Insert Data with ctx.em.mutator()

Use ctx.em.mutator(entity) for server-side inserts:

options: {
  seed: async (ctx) => {
    // Insert single record
    await ctx.em.mutator("todos").insertOne({
      title: "Welcome task",
      done: false,
    });

    // Insert multiple records
    await ctx.em.mutator("todos").insertMany([
      { title: "Learn Bknd basics", done: false },
      { title: "Create first entity", done: true },
      { title: "Set up authentication", done: false },
    ]);
  },
}

Step 3: Seed Related Entities

Insert parent records first, then children:

options: {
  seed: async (ctx) => {
    // Create users first
    const users = await ctx.em.mutator("users").insertMany([
      { email: "admin@example.com", name: "Admin" },
      { email: "user@example.com", name: "User" },
    ]);

    // Create posts referencing users
    await ctx.em.mutator("posts").insertMany([
      { title: "First Post", author_id: users[0].id },
      { title: "Second Post", author_id: users[1].id },
    ]);
  },
}

Step 4: Conditional Seeding

Check if data exists before seeding to avoid duplicates:

options: {
  seed: async (ctx) => {
    // Check if already seeded
    const existing = await ctx.em.repo("users").findOne({
      where: { email: { $eq: "admin@example.com" } },
    });

    if (existing) {
      console.log("Database already seeded");
      return;
    }

    // Seed data
    await ctx.em.mutator("users").insertOne({
      email: "admin@example.com",
      name: "Admin",
    });
  },
}

Full Example

import { type BunBkndConfig, serve } from "bknd/adapter/bun";
import { em, entity, text, boolean, number, date } from "bknd";

const schema = em({
  users: entity("users", {
    email: text().required().unique(),
    name: text(),
    role: text({ default_value: "user" }),
  }),
  posts: entity("posts", {
    title: text().required(),
    content: text(),
    published: boolean({ default_value: false }),
    view_count: number({ default_value: 0 }),
    created_at: date({ default_value: "now" }),
  }),
  tags: entity("tags", {
    name: text().required().unique(),
  }),
});

type Database = (typeof schema)["DB"];
declare module "bknd" {
  interface DB extends Database {}
}

const config: BunBkndConfig = {
  connection: { url: "file:data.db" },
  config: {
    data: schema.toJSON(),
  },
  options: {
    seed: async (ctx) => {
      // Check if already seeded
      const count = await ctx.em.repo("users").count();
      if (count > 0) {
        console.log("Skipping seed: data exists");
        return;
      }

      console.log("Seeding database...");

      // Seed users
      const [admin, author] = await ctx.em.mutator("users").insertMany([
        { email: "admin@example.com", name: "Admin", role: "admin" },
        { email: "author@example.com", name: "Author", role: "author" },
      ]);

      // Seed tags
      const tags = await ctx.em.mutator("tags").insertMany([
        { name: "javascript" },
        { name: "typescript" },
        { name: "bknd" },
      ]);

      // Seed posts
      await ctx.em.mutator("posts").insertMany([
        {
          title: "Getting Started with Bknd",
          content: "Learn the basics...",
          published: true,
          author_id: author.id,
        },
        {
          title: "Advanced Patterns",
          content: "Deep dive into...",
          published: false,
          author_id: admin.id,
        },
      ]);

      console.log("Seed complete!");
    },
  },
};

serve(config);

React/Browser Adapter

For browser-based apps using BkndBrowserApp:

import { BkndBrowserApp } from "bknd/adapter/browser";

function App() {
  return (
    <BkndBrowserApp
      config={{
        data: schema.toJSON(),
      }}
      options={{
        seed: async (ctx) => {
          await ctx.em.mutator("todos").insertMany([
            { title: "Sample task 1", done: false },
            { title: "Sample task 2", done: true },
          ]);
        },
      }}
    >
      <YourApp />
    </BkndBrowserApp>
  );
}

Environment-Based Seeding

Different data for dev vs production:

options: {
  seed: async (ctx) => {
    const isDev = process.env.NODE_ENV !== "production";

    // Always seed admin
    await ctx.em.mutator("users").insertOne({
      email: "admin@example.com",
      name: "Admin",
      role: "admin",
    });

    // Dev-only test data
    if (isDev) {
      await ctx.em.mutator("users").insertMany([
        { email: "test1@example.com", name: "Test User 1" },
        { email: "test2@example.com", name: "Test User 2" },
      ]);

      // Generate bulk test data
      const testPosts = Array.from({ length: 50 }, (_, i) => ({
        title: `Test Post ${i + 1}`,
        content: `Content for test post ${i + 1}`,
        published: i % 2 === 0,
      }));

      await ctx.em.mutator("posts").insertMany(testPosts);
    }
  },
}

Seed Execution Behavior

ScenarioSeed Runs?
First startup (empty DB)Yes
Subsequent startupsYes (every time)
After schema syncYes
Production deploymentYes (use guards!)

Important: The seed function runs on every startup. Always add existence checks to prevent duplicate data.

Mutator Methods Reference

MethodDescriptionExample
insertOne(data)Insert single recordmutator("users").insertOne({ email: "..." })
insertMany(data[])Insert multiple recordsmutator("users").insertMany([...])

Common Patterns

Idempotent Seeding

async function seedIfNotExists(ctx, entity: string, where: object, data: object) {
  const existing = await ctx.em.repo(entity).findOne({ where });
  if (!existing) {
    return ctx.em.mutator(entity).insertOne(data);
  }
  return existing;
}

// Usage
options: {
  seed: async (ctx) => {
    await seedIfNotExists(ctx, "users",
      { email: { $eq: "admin@example.com" } },
      { email: "admin@example.com", name: "Admin", role: "admin" }
    );
  },
}

Factory Functions

function createTestUser(overrides = {}) {
  return {
    email: `user${Date.now()}@test.com`,
    name: "Test User",
    role: "user",
    ...overrides,
  };
}

function createTestPost(authorId: number, overrides = {}) {
  return {
    title: "Test Post",
    content: "Lorem ipsum...",
    published: false,
    author_id: authorId,
    ...overrides,
  };
}

// Usage
options: {
  seed: async (ctx) => {
    const user = await ctx.em.mutator("users").insertOne(
      createTestUser({ role: "admin" })
    );

    await ctx.em.mutator("posts").insertMany([
      createTestPost(user.id, { title: "Post 1", published: true }),
      createTestPost(user.id, { title: "Post 2" }),
    ]);
  },
}

Seeding with Faker Data

import { faker } from "@faker-js/faker";

options: {
  seed: async (ctx) => {
    const users = Array.from({ length: 10 }, () => ({
      email: faker.internet.email(),
      name: faker.person.fullName(),
      role: faker.helpers.arrayElement(["user", "author", "admin"]),
    }));

    await ctx.em.mutator("users").insertMany(users);
  },
}

Common Pitfalls

Duplicate Data on Restart

Problem: Seed runs every startup, creating duplicates.

Fix: Check for existing data:

seed: async (ctx) => {
  const count = await ctx.em.repo("users").count();
  if (count > 0) return; // Already seeded

  // Seed logic...
}

Foreign Key Order

Problem: Foreign key constraint failed error.

Fix: Insert parent records before children:

// ❌ Wrong order
await ctx.em.mutator("posts").insertOne({ author_id: 1, ... }); // User doesn't exist!
await ctx.em.mutator("users").insertOne({ id: 1, ... });

// ✅ Correct order
const user = await ctx.em.mutator("users").insertOne({ ... });
await ctx.em.mutator("posts").insertOne({ author_id: user.id, ... });

Missing Required Fields

Problem: NOT NULL constraint failed error.

Fix: Include all required fields:

// ❌ Missing required field
await ctx.em.mutator("users").insertOne({ name: "Admin" });
// Error: email is required

// ✅ Include all required fields
await ctx.em.mutator("users").insertOne({
  email: "admin@example.com",  // required
  name: "Admin"
});

Seed in Production

Problem: Test data appears in production.

Fix: Guard with environment check:

seed: async (ctx) => {
  if (process.env.NODE_ENV === "production") {
    // Only seed essential data in production
    const adminExists = await ctx.em.repo("users").findOne({
      where: { role: { $eq: "admin" } },
    });
    if (!adminExists) {
      await ctx.em.mutator("users").insertOne({
        email: process.env.ADMIN_EMAIL,
        name: "Admin",
        role: "admin",
      });
    }
    return;
  }

  // Full dev seed...
}

Verification

After seeding, verify data was inserted:

seed: async (ctx) => {
  // ... insert data ...

  // Verify
  const userCount = await ctx.em.repo("users").count();
  const postCount = await ctx.em.repo("posts").count();

  console.log(`Seeded: ${userCount} users, ${postCount} posts`);
}

Or via API after startup:

const api = app.getApi();
const { data } = await api.data.readMany("users");
console.log("Users:", data.length);

DOs and DON'Ts

DO:

  • Check for existing data before inserting
  • Insert parent records before children (FK order)
  • Use environment checks for dev vs prod data
  • Log seed progress for debugging
  • Keep seed functions idempotent

DON'T:

  • Seed sensitive data (real passwords, API keys)
  • Assume seed runs only once
  • Hardcode production admin credentials in code
  • Skip required fields
  • Ignore foreign key relationships

Related Skills

  • bknd-crud-create - Create records via API/SDK
  • bknd-bulk-operations - Bulk insert/update/delete at runtime
  • bknd-create-entity - Define entities before seeding
  • bknd-define-relationship - Set up relations for seeding linked data

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

Coding

bknd-repo-search-with-opencode

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

bknd-client-setup

No summary provided by upstream source.

Repository SourceNeeds Review
General

btca-bknd-repo-learn

No summary provided by upstream source.

Repository SourceNeeds Review
General

bknd-bulk-operations

No summary provided by upstream source.

Repository SourceNeeds Review