S
Solo Kit
DocumentationComponentsPricingChangelogRoadmapFAQContact
LoginGet Started
DocumentationComponentsPricing
LoginGet Started
Welcome to Solo Kit DocumentationIntroductionTech StackRoadmapFAQGetting Started
Database OverviewSchema DesignDatabase QueriesDatabase MigrationsDatabase SeedingDatabase PerformanceDatabase SecurityBackup & RestoreDatabase MonitoringAdvanced Database Features
Database

Database Seeding

Database Seeding

Learn how to seed your Solo Kit database with demo data for development and testing. This guide covers seeding commands, data generation strategies, and best practices for managing test data.

🌱 Seeding Overview

What is Database Seeding?

Database seeding is the process of populating your database with initial data for:

  • Development Environment: Sample data to work with during development
  • Testing: Consistent data for automated tests
  • Demo Purposes: Realistic data for demonstrations or trials
  • Initial Setup: Required baseline data for application functionality

Solo Kit Seeding Strategy

Solo Kit implements a clean development approach with optional seeding:

🏗️ Fresh Database (pnpm db:push)
    ↓ (optional)
📊 Demo Data (pnpm seed:demo)
    ↓ (development)
🧪 Test Data (custom scripts)

Key Principles:

  • Clean by default: Fresh installations start with empty schemas
  • Optional demo data: Add sample data when you need it
  • Reproducible seeding: Reset and reseed at any time
  • Environment-aware: Different data for different environments

🛠️ Seeding Commands

Available Commands

# Add demo data to your database
pnpm seed:demo

# Clear all data from database (keeps schema)
pnpm seed:clear

# Reset: clear all data and reseed with demo data
pnpm seed:reset

Command Locations

Root-level commands:

# These commands work from the project root
pnpm seed:demo
pnpm seed:clear  
pnpm seed:reset

Database package commands:

# Run directly in packages/database/
cd packages/database
pnpm seed:demo
pnpm seed:clear
pnpm seed:reset

📝 Seeding Implementation

Current Status

Solo Kit is designed with seeding commands defined in packages/database/package.json:

{
  "scripts": {
    "seed:demo": "tsx ../../scripts/seed.ts demo",
    "seed:clear": "tsx ../../scripts/seed.ts clear", 
    "seed:reset": "tsx ../../scripts/seed.ts reset"
  }
}

Implementation Note: The seeding scripts are configured but not yet implemented. This follows Solo Kit's progressive enhancement approach - commands are ready when you need to implement seeding functionality.

Seeding Architecture

Planned seeding structure:

// scripts/seed.ts (to be implemented)
import { db } from '@packages/database';
import { users, userPreferences, subscriptions } from '@packages/database/schema';

async function seedDemo() {
  console.log('🌱 Seeding demo data...');
  
  // Seed users
  const demoUsers = await db.insert(users).values([
    // Demo user data
  ]).returning();
  
  // Seed related data
  // ... other seeding logic
  
  console.log('✅ Demo data seeded successfully!');
}

🎯 Custom Seeding Implementation

Creating a Seed Script

When you're ready to implement seeding, create the seed script:

// scripts/seed.ts
import { db } from '@packages/database';
import { 
  users, 
  userPreferences,
  subscriptions,
  verificationTokens 
} from '@packages/database/schema';
import { eq } from 'drizzle-orm';

const DEMO_USERS = [
  {
    id: 'demo-user-1',
    email: 'demo@example.com',
    name: 'Demo User',
    firstName: 'Demo',
    lastName: 'User',
    displayName: 'Demo User',
    bio: 'A demo user for testing the application',
    timezone: 'UTC',
    language: 'en',
  },
  {
    id: 'demo-admin-1', 
    email: 'admin@example.com',
    name: 'Admin User',
    role: 'admin' as const,
    firstName: 'Admin',
    lastName: 'User',
    displayName: 'Admin User',
    bio: 'Administrator account for testing',
    timezone: 'UTC',
    language: 'en',
  },
];

export async function seedDemo() {
  console.log('🌱 Starting demo data seeding...');
  
  try {
    const database = await db;
    if (!database) {
      throw new Error('Database not available');
    }

    // Seed users
    console.log('👥 Seeding demo users...');
    const seededUsers = await database
      .insert(users)
      .values(DEMO_USERS)
      .onConflictDoNothing({ target: users.email })
      .returning();

    // Seed user preferences for each user
    console.log('⚙️ Seeding user preferences...');
    for (const user of seededUsers) {
      await database
        .insert(userPreferences)
        .values({
          userId: user.id,
          theme: 'system',
          emailNotifications: true,
          marketingEmails: false,
        })
        .onConflictDoNothing({ target: userPreferences.userId });
    }

    // Seed subscriptions (if needed)
    console.log('💳 Seeding demo subscriptions...');
    if (seededUsers.length > 0) {
      await database
        .insert(subscriptions) 
        .values([
          {
            userId: seededUsers[0].id,
            status: 'active',
            stripeSubscriptionId: 'sub_demo_active',
          },
        ])
        .onConflictDoNothing({ target: subscriptions.stripeSubscriptionId });
    }

    console.log(`✅ Demo seeding completed! Created ${seededUsers.length} users.`);
    
  } catch (error) {
    console.error('❌ Demo seeding failed:', error);
    throw error;
  }
}

export async function clearData() {
  console.log('🧹 Clearing all database data...');
  
  try {
    const database = await db;
    if (!database) {
      throw new Error('Database not available');
    }

    // Delete in order to respect foreign key constraints
    await database.delete(verificationTokens);
    await database.delete(subscriptions);  
    await database.delete(userPreferences);
    await database.delete(users);

    console.log('✅ Database cleared successfully!');
    
  } catch (error) {
    console.error('❌ Database clearing failed:', error);
    throw error;
  }
}

export async function resetData() {
  console.log('🔄 Resetting database with fresh demo data...');
  
  await clearData();
  await seedDemo();
  
  console.log('✅ Database reset completed!');
}

// Command line interface
async function main() {
  const command = process.argv[2];
  
  switch (command) {
    case 'demo':
      await seedDemo();
      break;
    case 'clear': 
      await clearData();
      break;
    case 'reset':
      await resetData();
      break;
    default:
      console.log('Usage: tsx seed.ts [demo|clear|reset]');
      process.exit(1);
  }
  
  process.exit(0);
}

if (import.meta.url === new URL(import.meta.resolve('./seed.ts')).href) {
  main().catch((error) => {
    console.error('Seed script failed:', error);
    process.exit(1);
  });
}

Environment-Aware Seeding

Different data for different environments:

// Environment-specific seeding
const isDevelopment = process.env.NODE_ENV === 'development';
const isStaging = process.env.NODE_ENV === 'staging';
const isProduction = process.env.NODE_ENV === 'production';

export async function seedEnvironment() {
  if (isDevelopment) {
    await seedDevelopmentData();
  } else if (isStaging) {
    await seedStagingData();
  } else if (isProduction) {
    // Never seed production automatically
    console.warn('⚠️ Production seeding disabled for safety');
    return;
  }
}

async function seedDevelopmentData() {
  // Rich demo data with multiple users, posts, etc.
  await seedDemo();
  await seedAdditionalTestData();
}

async function seedStagingData() {
  // Minimal data that mirrors production structure
  await seedMinimalData();
}

📊 Seeding Patterns

User-Centric Seeding

Create realistic user hierarchies:

const DEMO_DATA = {
  // Regular users
  users: [
    {
      email: 'user1@example.com',
      name: 'Alice Johnson',
      role: 'user',
      firstName: 'Alice',
      lastName: 'Johnson',
    },
    {
      email: 'user2@example.com', 
      name: 'Bob Smith',
      role: 'user',
      firstName: 'Bob',
      lastName: 'Smith',
    },
  ],
  
  // Admin users
  admins: [
    {
      email: 'admin@example.com',
      name: 'Super Admin',
      role: 'admin',
      firstName: 'Super',
      lastName: 'Admin',
    },
  ],
};

Relational Data Seeding

Maintain referential integrity:

async function seedRelationalData() {
  // 1. Create parent entities first
  const users = await database.insert(users).values(DEMO_USERS).returning();
  
  // 2. Create dependent entities with proper references
  const preferences = users.map(user => ({
    userId: user.id,
    theme: 'system',
    emailNotifications: true,
  }));
  
  await database.insert(userPreferences).values(preferences);
  
  // 3. Create business entities
  const subscriptions = users.slice(0, 2).map(user => ({
    userId: user.id,
    status: 'active',
    stripeSubscriptionId: `sub_demo_${user.id}`,
  }));
  
  await database.insert(subscriptions).values(subscriptions);
}

Conflict Resolution

Handle duplicate seeding gracefully:

// Use onConflictDoNothing for idempotent seeding
await database
  .insert(users)
  .values(DEMO_USERS)
  .onConflictDoNothing({ target: users.email })
  .returning();

// Or update existing data
await database
  .insert(users)
  .values(DEMO_USERS)
  .onConflictDoUpdate({
    target: users.email,
    set: {
      name: sql`excluded.name`,
      updatedAt: new Date(),
    },
  })
  .returning();

🧪 Testing Data Strategies

Test-Specific Seeding

Create focused test data:

// tests/helpers/seed-test-data.ts
export async function seedUserTest(): Promise<User> {
  const [user] = await db
    .insert(users)
    .values({
      email: `test-${Date.now()}@example.com`,
      name: 'Test User',
      firstName: 'Test',
      lastName: 'User',
    })
    .returning();
    
  return user;
}

export async function seedSubscriptionTest(userId: string): Promise<Subscription> {
  const [subscription] = await db
    .insert(subscriptions)
    .values({
      userId,
      status: 'active',
      stripeSubscriptionId: `sub_test_${Date.now()}`,
    })
    .returning();
    
  return subscription;
}

Test Data Cleanup

Ensure clean test isolation:

// tests/helpers/cleanup.ts
export async function cleanupTestData() {
  const database = await db;
  
  // Clean up test data (identifiable by email pattern)
  await database
    .delete(users)
    .where(sql`email LIKE 'test-%@example.com'`);
}

// In your test files
import { afterEach } from 'vitest';
import { cleanupTestData } from './helpers/cleanup';

afterEach(async () => {
  await cleanupTestData();
});

🚀 Production Considerations

Production Seeding Safety

Never automatically seed production:

// Always require explicit confirmation for production
async function seedProduction() {
  if (process.env.NODE_ENV === 'production') {
    const readline = require('readline').createInterface({
      input: process.stdin,
      output: process.stdout,
    });
    
    const answer = await new Promise<string>(resolve => {
      readline.question(
        '⚠️  You are about to seed PRODUCTION data. Type "yes" to confirm: ',
        resolve
      );
    });
    
    if (answer !== 'yes') {
      console.log('❌ Production seeding cancelled');
      process.exit(1);
    }
    
    readline.close();
  }
  
  // Proceed with seeding...
}

Data Migration vs Seeding

Understand the difference:

OperationPurposeWhen to UseSafety
MigrationSchema changesVersion upgradesProduction-safe
SeedingSample dataDevelopment/testingDevelopment-only

Migrations change structure, seeding adds data.

📈 Advanced Seeding

Realistic Data Generation

Use libraries for realistic data:

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

function generateRealisticUsers(count: number) {
  return Array.from({ length: count }, (_, i) => ({
    id: `demo-user-${i + 1}`,
    email: faker.internet.email(),
    name: faker.person.fullName(),
    firstName: faker.person.firstName(),
    lastName: faker.person.lastName(),
    bio: faker.lorem.sentence(),
    avatarUrl: faker.image.avatar(),
    timezone: faker.location.timeZone(),
    language: faker.helpers.arrayElement(['en', 'es', 'fr', 'de']),
    createdAt: faker.date.past(),
  }));
}

// Generate 50 realistic demo users
const REALISTIC_USERS = generateRealisticUsers(50);

Progressive Data Volume

Scale data based on needs:

const SEEDING_MODES = {
  minimal: { users: 2, subscriptions: 1 },
  standard: { users: 10, subscriptions: 5 },
  extensive: { users: 100, subscriptions: 25 },
  stress: { users: 1000, subscriptions: 200 },
};

async function seedByMode(mode: keyof typeof SEEDING_MODES) {
  const config = SEEDING_MODES[mode];
  
  const users = generateRealisticUsers(config.users);
  const seededUsers = await database.insert(users).values(users).returning();
  
  // Generate proportional subscriptions
  const subscriptionUsers = seededUsers.slice(0, config.subscriptions);
  // ... generate subscription data
}

// Usage: pnpm seed:demo extensive

Seeding Performance

Optimize for large datasets:

async function seedLargeDataset() {
  const BATCH_SIZE = 100;
  const users = generateRealisticUsers(1000);
  
  // Process in batches to avoid memory issues
  for (let i = 0; i < users.length; i += BATCH_SIZE) {
    const batch = users.slice(i, i + BATCH_SIZE);
    
    await database
      .insert(users)
      .values(batch)
      .onConflictDoNothing({ target: users.email });
      
    console.log(`Seeded batch ${Math.floor(i / BATCH_SIZE) + 1}/${Math.ceil(users.length / BATCH_SIZE)}`);
  }
}

🔧 Development Workflow

Daily Development

# Start fresh each day
pnpm db:push          # Ensure schema is current
pnpm seed:demo        # Add demo data

# Work with data
pnpm db:studio        # Browse/modify data

# Reset when needed
pnpm seed:reset       # Clear and reseed

Feature Development

# Create feature branch
git checkout -b feature/user-profiles

# Ensure clean data
pnpm seed:reset

# Add specific test data for your feature
# (custom seeding in your dev script)

# Test feature with realistic data
pnpm dev

Testing Workflow

# Before running tests
pnpm seed:clear       # Start with empty database
pnpm test             # Tests create their own data

# Or with baseline data
pnpm seed:demo        # Standard demo data
pnpm test             # Tests work with existing data

🎯 Next Steps

Expand your database management with these guides:

  1. Migrations - Evolve your schema safely
  2. Performance - Optimize query performance
  3. Security - Secure your data
  4. Monitoring - Monitor database health

Database seeding provides the foundation for effective development and testing with Solo Kit's PostgreSQL-first architecture.

Database Migrations

Manage database schema evolution safely with Drizzle migrations, from development to production deployments

Database Performance

Optimize database queries, implement effective indexing, and monitor performance in your Solo Kit application

On this page

Database Seeding🌱 Seeding OverviewWhat is Database Seeding?Solo Kit Seeding Strategy🛠️ Seeding CommandsAvailable CommandsCommand Locations📝 Seeding ImplementationCurrent StatusSeeding Architecture🎯 Custom Seeding ImplementationCreating a Seed ScriptEnvironment-Aware Seeding📊 Seeding PatternsUser-Centric SeedingRelational Data SeedingConflict Resolution🧪 Testing Data StrategiesTest-Specific SeedingTest Data Cleanup🚀 Production ConsiderationsProduction Seeding SafetyData Migration vs Seeding📈 Advanced SeedingRealistic Data GenerationProgressive Data VolumeSeeding Performance🔧 Development WorkflowDaily DevelopmentFeature DevelopmentTesting Workflow🎯 Next Steps