linked-faker
Generate realistic relational test data with valid relationships — from one declarative schema.
Random values give you one field at a time. linked-faker gives you a whole dataset — users, orders, products — wired together with valid foreign keys.
No manual IDs. No seed scripts. No broken foreign keys.
Installation
npm install linked-faker
Zero required dependencies. Built-in field generators included.
Quick Start
import { defineSchema, generate, oneOf, number, faker } from 'linked-faker';
const schema = defineSchema({
users: {
count: 10,
idPrefix: 'usr',
fields: {
name: 'person.fullName', // built-in path
email: faker('internet.email'), // helper API
createdAt: 'date.past',
},
},
products: {
count: 20,
idPrefix: 'prd',
fields: {
name: 'commerce.productName',
price: 'commerce.price',
},
},
orders: {
count: 50,
idPrefix: 'ord',
fields: {
orderedAt: 'date.recent',
},
relations: {
userId: { ref: 'users' },
productIds: { ref: 'products', many: true, min: 1, max: 4 },
},
},
});
const data = generate(schema);
console.log(data.users[0]);
console.log(data.orders[0]);
// { id: 'ord_1', orderedAt: '...', userId: 'usr_7', productIds: ['prd_2', 'prd_9'] }
Every userId in orders is guaranteed to match a real generated id in users. No manual wiring required.
Core Concepts
Entities
Each top-level key in your schema (users, products, orders) is an entity — think of it as a database table.
Fields
Each entity has fields, mapped to built-in generator paths, helper descriptors, custom functions, or static values.
Relations
Relations describe how one entity's records point to another — the core feature that turns random fields into a coherent dataset.
IDs
By default, every generated record gets an auto id field (usr_1, usr_2, …). Customize the format per entity with idPrefix.
API Reference
defineSchema(schemaObject)
Defines the shape of your dataset. Validates the schema and returns it for use with generate().
defineSchema({
[entityName: string]: {
count?: number;
countPerParent?: { ref: string; min: number; max: number };
idPrefix?: string;
fields?: Record<string, string | ((record, index) => unknown) | unknown>;
relations?: Record<string, RelationConfig>;
};
});
| Option | Type | Default | Description |
|---|---|---|---|
count |
number |
— | Number of records to generate (required unless countPerParent is set) |
countPerParent |
object |
— | Generate a variable number of child records per parent |
idPrefix |
string |
first 3 chars of entity name | Prefix for generated IDs (usr_1, prd_1, etc.) |
fields |
object |
{} |
Field name → generator mapping |
relations |
object |
{} |
Field name → relation config |
generate(schema, options?)
Generates the full dataset based on the schema. Entities are generated in topological order to satisfy relationship dependencies — you don't need to declare entities in a particular order.
generate(schema, {
seed?: number; // reproducible output
validate?: boolean; // return { data, validation, valid }
});
Returns: an object keyed by entity name, each containing an array of generated records.
{
users: [{ ... }, { ... }],
products: [{ ... }, { ... }],
orders: [{ ... }, { ... }],
}
Field Generators
Fields accept:
1. A built-in generator path
fields: {
name: 'person.fullName',
email: 'internet.email',
price: 'commerce.price',
}
2. A helper descriptor
fields: {
status: oneOf(['pending', 'paid', 'cancelled']),
total: number({ min: 10, max: 500 }),
email: faker('internet.email'),
}
3. A custom function (receives the record being built and its index)
fields: {
username: (record, index) => `user_${index}`,
isActive: () => Math.random() > 0.2,
}
4. A fixed/static value
fields: {
role: 'customer',
}
Relations
Belongs-to (single reference)
relations: {
userId: { ref: 'users' },
}
Many-to-many
relations: {
productIds: { ref: 'products', many: true, min: 1, max: 5 },
}
Filtered relation
relations: {
userId: {
ref: 'users',
filter: (user) => user.isActive === true,
},
}
Cascading counts (each user has 0–5 orders)
orders: {
countPerParent: { ref: 'users', min: 0, max: 5 },
idPrefix: 'ord',
fields: { orderedAt: 'date.recent' },
relations: {
userId: { ref: 'users', linkedToParent: true },
},
},
Export Formats
Export generated data to common formats for seeding a real database:
import { generate, exportAs } from 'linked-faker';
const data = generate(schema);
await exportAs(data, 'json', './seed-data.json');
await exportAs(data, 'csv', './seed-data/'); // one CSV file per entity
await exportAs(data, 'sql', './seed.sql', {
dialect: 'postgres', // or 'mysql', 'sqlite'
});
| Format | Output |
|---|---|
json |
Single JSON file, keyed by entity |
csv |
One .csv file per entity in a target folder |
sql |
INSERT INTO ... statements, dialect-aware |
Recipes
Reproducible datasets
const data = generate(schema, { seed: 42 });
Same schema + same seed → same output every run. Different seeds produce different datasets.
Validation
const result = generate(schema, { seed: 42, validate: true });
console.log(result.valid); // true
console.log(result.validation); // []
console.log(result.data); // generated dataset
Seeding a database manually
Use the generated arrays directly with your ORM or query builder:
const data = generate(schema);
await db.insert(usersTable).values(data.users);
await db.insert(productsTable).values(data.products);
await db.insert(ordersTable).values(data.orders);
Multi-level relationships
const schema = defineSchema({
categories: {
count: 5,
idPrefix: 'cat',
fields: { name: 'commerce.department' },
},
products: {
count: 30,
idPrefix: 'prd',
fields: { name: 'commerce.productName' },
relations: { categoryId: { ref: 'categories' } },
},
orders: {
count: 100,
idPrefix: 'ord',
relations: {
productIds: { ref: 'products', many: true, min: 1, max: 3 },
},
},
});
Roadmap
See VERSIONS.md for the full version plan (v1, v2, v3).
Upcoming highlights:
- CLI:
npx linked-faker generate schema.json --out ./seed - Self-referencing relations (e.g.
Employee.managerId → Employee) - TypeScript type inference from schema
- Plugin system for custom field-generator libraries
- Web-based schema builder UI
How does it decide generation order?
It builds a dependency graph from your relations and countPerParent config, then generates entities in topological order. Declare entities in any order in your schema.
License
MIT