Random Data for Database Seeding — A Developer's Guide
Database seeding fills your development and staging databases with realistic test data so you can build features, run demos, and test queries against something that actually looks like production. This guide explains the different approaches to seeding, what data types each table needs, and how to maintain referential integrity across related tables.
What Is Database Seeding?
Database seeding is the process of populating database tables with initial data. In development, this means filling tables with fake but realistic records so your application has something to display, query, and paginate against. Without seeding, every developer on the team starts with an empty database and has to manually create test records before they can work on anything.
Seeding is different from migration. Migrations define the database structure (tables, columns, indexes). Seeders fill that structure with data. Most frameworks keep them separate — you run migrations first, then seeders.
Why Seed with Realistic Data
Seeding with "test1", "test2", "aaa@bbb.com" creates problems that realistic data avoids:
- UI layout bugs — Real names like "Christopher Alexander Wellington III" are much longer than "test1". If your layout breaks on long names, you want to find out in development, not production.
- Query performance — Queries behave differently when every row has the same value vs. distributed unique values. Realistic data gives you meaningful query plans.
- Demo readiness — A staging environment with realistic data is always ready for client demos. No scrambling to make it look presentable.
- Sorting and filtering — Features like alphabetical sorting, search-as-you-type, and faceted filtering only make sense to test with varied, realistic data.
Approaches to Database Seeding
1. Manual SQL Inserts
The simplest approach — write INSERT statements by hand. This works for small reference tables (countries, categories, roles) but does not scale for generating hundreds of user records.
INSERT INTO users (first_name, last_name, email, phone) VALUES
('Elena', 'Rodriguez', 'elena.rodriguez@example.com', '+1-555-0147'),
('James', 'Okonkwo', 'j.okonkwo@example.com', '+1-555-0283'),
('Yuki', 'Tanaka', 'yuki.tanaka@example.com', '+81-3-5555-0192');
2. Framework Factories (Laravel, Rails, Django)
Modern frameworks integrate with Faker libraries through factory patterns. Define once, generate thousands of records with a single command.
// Laravel Factory (PHP)
class UserFactory extends Factory {
public function definition(): array {
return [
'name' => fake()->name(),
'email' => fake()->unique()->safeEmail(),
'phone' => fake()->phoneNumber(),
'address' => fake()->streetAddress(),
'city' => fake()->city(),
'state' => fake()->stateAbbr(),
'zip' => fake()->postcode(),
'created_at' => fake()->dateTimeBetween('-2 years'),
];
}
}
// In your seeder:
User::factory(500)->create();
# Django seeder with Faker (Python)
from faker import Faker
from myapp.models import User
fake = Faker()
def seed_users(count=500):
users = []
for _ in range(count):
users.append(User(
first_name=fake.first_name(),
last_name=fake.last_name(),
email=fake.unique.email(),
phone=fake.phone_number(),
))
User.objects.bulk_create(users)
3. Online Generators + CSV Import
Generate data from FakeMyInfo (names, addresses, emails, phone numbers), export as CSV, then import directly into your database. This approach requires zero code and works with any database.
-- PostgreSQL: import CSV
COPY users(first_name, last_name, email, phone)
FROM '/path/to/users.csv'
DELIMITER ','
CSV HEADER;
-- MySQL: import CSV
LOAD DATA INFILE '/path/to/users.csv'
INTO TABLE users
FIELDS TERMINATED BY ','
ENCLOSED BY '"'
LINES TERMINATED BY '\n'
IGNORE 1 ROWS;
4. Faker Libraries in Scripts
Write a standalone Node.js or Python script that generates data and inserts it directly. Useful when you need more control than a framework factory provides but do not want to write SQL by hand.
// Node.js seeder with @faker-js/faker
import { faker } from '@faker-js/faker';
import mysql from 'mysql2/promise';
const db = await mysql.createConnection({ host: 'localhost', user: 'root', database: 'myapp' });
for (let i = 0; i < 500; i++) {
await db.execute(
'INSERT INTO users (name, email, phone, city) VALUES (?, ?, ?, ?)',
[faker.person.fullName(), faker.internet.email(), faker.phone.number(), faker.location.city()]
);
}
console.log('Seeded 500 users');
Data Types You Need per Table
Users Table
First name, last name, email (unique), hashed password placeholder, phone number, date of birth, registration date. Use the Name Generator and Email Generator for these fields.
Addresses Table
Street address, city, state/province, postal code, country. Each country has its own format — a US address has a 5-digit ZIP, a UK address has an alphanumeric postcode, and a German address puts the PLZ before the city. Use the Address Generator to get correctly formatted addresses per country.
Orders Table
Order ID, user ID (foreign key), order date, total amount, status (pending/shipped/delivered/cancelled), shipping address ID. The user ID must reference a real row in the users table — this is where referential integrity matters.
Products Table
Product name, description, price, SKU, category, stock quantity. Use the Company Generator for brand names and combine with product type suffixes for realistic product names.
Maintaining Referential Integrity
The trickiest part of database seeding is keeping foreign keys valid. Here is the correct order:
- Seed independent tables first — users, products, categories (tables with no foreign keys)
- Collect generated IDs — after inserting users, query back the IDs you just created
- Seed dependent tables — orders, reviews, line items, using the IDs from step 2
- Seed junction tables last — user_roles, product_categories, order_items
// Maintaining foreign keys in Node.js
const userIds = [];
for (let i = 0; i < 100; i++) {
const [result] = await db.execute(
'INSERT INTO users (name, email) VALUES (?, ?)',
[faker.person.fullName(), faker.internet.email()]
);
userIds.push(result.insertId);
}
// Now seed orders referencing real user IDs
for (let i = 0; i < 300; i++) {
const userId = faker.helpers.arrayElement(userIds);
await db.execute(
'INSERT INTO orders (user_id, total, status, created_at) VALUES (?, ?, ?, ?)',
[userId, faker.commerce.price({ min: 10, max: 500 }), faker.helpers.arrayElement(['pending','shipped','delivered']), faker.date.recent({ days: 90 })]
);
}
Using FakeMyInfo's Bulk Export for Seeding
FakeMyInfo generators support bulk generation with JSON and CSV export. Here is a practical workflow:
- Open the Fake Name Generator — generate 100 names, export as CSV
- Open the Fake Email Generator — generate 100 emails, export as CSV
- Open the Fake Address Generator — select your target country, generate 100 addresses, export as CSV
- Combine the CSV files (paste columns side by side in a spreadsheet, or use a script)
- Import the combined CSV into your database with
COPY(PostgreSQL) orLOAD DATA INFILE(MySQL)
This approach requires zero code, works with any database, and gives you fully formatted, locale-appropriate data in minutes.
Performance Considerations for Large Datasets
When seeding tens of thousands of rows, individual INSERT statements are painfully slow. Use these techniques to speed things up:
- Batch inserts — Insert 100-500 rows per statement instead of one at a time. Most databases handle multi-row INSERTs orders of magnitude faster.
- Disable indexes temporarily — Drop non-essential indexes before seeding, then recreate them after. Index maintenance on every insert is expensive.
- Use transactions — Wrap your entire seeding operation in a single transaction. This avoids the overhead of autocommit on every row.
- Use COPY/LOAD DATA — Bulk import commands bypass the SQL parser and are the fastest way to load data from files.
- Disable foreign key checks — Temporarily disable FK checks during seeding (e.g.,
SET FOREIGN_KEY_CHECKS=0in MySQL), then re-enable after. Only do this if you are confident your data is consistent.
Rule of thumb: If your seeder takes more than 30 seconds, you are probably inserting one row at a time. Switch to batch inserts or bulk import and it will finish in seconds.
All FakeMyInfo Generators
Frequently Asked Questions
For most applications, 100-1,000 rows per table is enough for development. For performance testing, aim for production-scale volumes — if your production users table has 500K rows, seed at least 100K to catch query performance issues. Start small and scale up as needed.
Yes. Generate data from FakeMyInfo or similar tools, export as CSV, then import using your database's bulk import command — COPY in PostgreSQL, LOAD DATA INFILE in MySQL, or .import in SQLite. This is often faster than writing a seeder script for one-time data loads.
Seed parent tables first (users, products, categories), then reference their generated IDs when seeding child tables (orders, reviews, line items). Most ORM factory systems handle this automatically. For manual seeding, use subqueries or generate IDs in a predictable sequence.