TypeScript-first mock database generator for rapid application development
npm install @doviui/dev-dbTypeScript-first mock database generator for rapid application development
dev-db eliminates the friction of setting up databases during development. Define your data model with a type-safe schema, generate realistic mock data instantly, and iterate faster without database infrastructure overhead.
For Frontend Developers: Build and test UI components with realistic data without waiting for backend APIs. Generate hundreds of records in seconds and work independently.
For Backend Developers: Prototype schemas, test business logic, and validate data models before committing to a database. Catch schema errors early with built-in validation.
For Teams: Share reproducible datasets across development, testing, and CI/CD environments using seeds. Onboard new developers instantly with pre-generated data.
- Type-Safe Schema Definition - Fluent TypeScript API with full IntelliSense support
- TypeScript Types Generation - Auto-generate type definitions from schemas for type-safe data consumption
- Automatic Relationship Resolution - Foreign keys handled intelligently with topological sorting
- Production-Quality Mock Data - Powered by Faker.js for realistic, diverse datasets
- Built-In Validation - Detect circular dependencies, missing tables, and constraint conflicts before generation
- Reproducible Datasets - Seed-based generation for consistent data across environments
- Zero Configuration - Works out of the box, no database setup required
``bash`
bun add @doviui/dev-dbor
npm install @doviui/dev-dbor
yarn add @doviui/dev-dbor
pnpm add @doviui/dev-db
Create schema files using the fluent TypeScript API:
`typescript
// schemas/user.schema.ts
import { t } from '@doviui/dev-db'
export default {
User: {
$count: 100, // Generate 100 users
id: t.bigserial().primaryKey(),
username: t.varchar(50).unique().notNull().generate('internet.userName'),
email: t.varchar(255).unique().notNull().generate('internet.email'),
full_name: t.text().generate('person.fullName'),
age: t.integer().min(18).max(90),
is_active: t.boolean().default(true),
created_at: t.timestamptz().default('now')
}
}
`
`typescript
// schemas/post.schema.ts
import { t } from '@doviui/dev-db'
export default {
Post: {
$count: 500, // Generate 500 posts
id: t.bigserial().primaryKey(),
user_id: t.foreignKey('User', 'id').notNull(),
title: t.varchar(200).generate('lorem.sentence'),
content: t.text().generate('lorem.paragraphs'),
status: t.varchar(20).enum(['draft', 'published', 'archived']).default('draft'),
view_count: t.integer().default(0),
created_at: t.timestamptz().default('now')
}
}
`
#### Using the CLI directly
Run the CLI to generate JSON files from a single schema file or a directory:
`bashGenerate from a single schema file
bunx @doviui/dev-db schema.ts
Directory Support: When a directory is provided, all
.ts and .js files are loaded and their schemas are merged. This allows you to organize related tables across multiple files for better maintainability.#### Adding to package.json scripts
Add generation to your project's scripts:
`json
{
"scripts": {
"generate:data": "bunx @doviui/dev-db ./schemas -o ./mock-data",
"generate:data:seed": "bunx @doviui/dev-db ./schemas -o ./mock-data -s 42"
}
}
`Then run with:
`bash
bun run generate:data
or with a seed
bun run generate:data:seed
`$3
Import the generated JSON and use it in your application:
`typescript
// server.ts
import users from './mock-data/User.json'
import posts from './mock-data/Post.json'// Simple query helpers
function getUserById(id: number) {
return users.find(u => u.id === id)
}
function getPostsByUserId(userId: number) {
return posts.filter(p => p.user_id === userId)
}
// Use in your API
app.get('/users/:id', (req, res) => {
const user = getUserById(parseInt(req.params.id))
if (!user) return res.status(404).json({ error: 'User not found' })
const userPosts = getPostsByUserId(user.id)
res.json({ ...user, posts: userPosts })
})
`$3
Generate TypeScript type definitions from your schemas for full type safety when consuming the mock data:
`bash
Generate types alongside JSON data
bunx @doviui/dev-db schema.ts --types
`This creates a
types.ts file with interfaces matching your schema:`typescript
// mock-data/types.ts (auto-generated)
export interface User {
id: number;
username: string;
email: string;
age: number | null;
created_at?: string;
}export interface Post {
id: number;
user_id: number; // Correctly typed based on User.id
title: string;
content: string | null;
created_at?: string;
}
`Import and use the types in your application:
`typescript
// server.ts
import users from './mock-data/User.json'
import posts from './mock-data/Post.json'
import type { User, Post } from './mock-data/types'// Fully type-safe!
function getUserById(id: number): User | undefined {
return users.find(u => u.id === id)
}
function getPostsByUserId(userId: number): Post[] {
return posts.filter(p => p.user_id === userId)
}
`Programmatic API:
`typescript
import { TypesGenerator } from '@doviui/dev-db'const typesGenerator = new TypesGenerator(schema, {
outputDir: './mock-data',
fileName: 'types.ts' // Optional, defaults to 'types.ts'
})
await typesGenerator.generate()
`API Reference
$3
dev-db supports a comprehensive set of SQL-inspired data types:
#### Numeric Types
`typescript
t.bigint() // Big integer
t.bigserial() // Auto-incrementing big integer
t.integer() // Standard integer
t.smallint() // Small integer
t.serial() // Auto-incrementing integer
t.decimal(10, 2) // Decimal with precision and scale
t.numeric(10, 2) // Alias for decimal
t.real() // Floating point
t.double() // Double precision float
`#### String Types
`typescript
t.varchar(255) // Variable-length string
t.char(10) // Fixed-length string
t.text() // Unlimited text
`#### Date/Time Types
`typescript
t.date() // Date only
t.time() // Time only
t.timestamp() // Date and time
t.timestamptz() // Date and time with timezone
`#### Other Types
`typescript
t.boolean() // True/false
t.uuid() // UUID v4// JSON types with optional structured schemas
t.json() // JSON object (unstructured)
t.jsonb() // JSON binary (unstructured)
// Structured JSON with type-safe schema
t.json({
id: t.integer(),
name: t.varchar(100),
preferences: t.json({
theme: t.varchar(20),
notifications: t.boolean()
})
})
`#### Relationships
`typescript
t.foreignKey('TableName', 'column') // Foreign key reference
t.belongsTo('TableName', 'foreignKeyField') // Many-to-one relationship
t.hasMany('TableName', 'foreignKeyField', { min: 1, max: 5 }) // One-to-many relationship (virtual)
`$3
Chain modifiers to configure field behavior and constraints:
`typescript
// Constraints
.primaryKey() // Mark as primary key
.unique() // Enforce uniqueness
.notNull() // Cannot be null
.nullable() // Can be null (default)// Defaults
.default(value) // Set default value
.default('now') // Special: current timestamp
// Ranges (for numeric types)
.min(18) // Minimum value
.max(90) // Maximum value
// Enums
.enum(['draft', 'published', 'archived'])
// Custom generation
.generate('internet.email') // Use Faker.js method
.generate(() => Math.random() * 100) // Custom function
`Advanced Usage
$3
For clearer relationship semantics and automatic record count calculation, use
hasMany() and belongsTo() helpers:`typescript
import { t } from '@doviui/dev-db'export default {
User: {
$count: 100,
id: t.bigserial().primaryKey(),
username: t.varchar(50).unique().generate('internet.userName'),
// Define one-to-many: each user has 2-5 posts
posts: t.hasMany('Post', 'userId', { min: 2, max: 5 })
},
Post: {
// $count is automatically calculated: 100 users * avg(2,5) posts = 350 posts
id: t.bigserial().primaryKey(),
userId: t.integer(), // The actual foreign key field
// Define many-to-one for clearer intent
author: t.belongsTo('User', 'userId'),
title: t.varchar(200).generate('lorem.sentence'),
content: t.text().generate('lorem.paragraphs')
}
}
`Key Benefits:
- Auto-calculated counts:
hasMany automatically calculates child table record counts based on parent count and min/max constraints
- Clearer intent: belongsTo makes many-to-one relationships more explicit than raw foreignKey
- Virtual fields: Both hasMany and belongsTo fields don't appear in generated output—they're metadata for generation logicHow it works:
1.
hasMany('Post', 'userId', { min: 2, max: 5 }) tells the generator: "Each User should have between 2-5 Posts"
2. The generator calculates: 100 users average(2, 5) = 100 3.5 = 350 posts
3. belongsTo('User', 'userId') instructs the generator to populate the userId field with valid User IDs
4. Neither posts nor author appear in the generated JSON—only the actual data fieldsComplex Example:
`typescript
export default {
Author: {
$count: 50,
id: t.uuid().primaryKey(),
name: t.varchar(100).generate('person.fullName'),
articles: t.hasMany('Article', 'authorId', { min: 3, max: 10 })
}, Article: {
// Auto-calculated: 50 * 6.5 = 325 articles
id: t.bigserial().primaryKey(),
authorId: t.varchar(36), // UUID foreign key
author: t.belongsTo('Author', 'authorId'),
title: t.varchar(200),
comments: t.hasMany('Comment', 'articleId', { min: 0, max: 20 })
},
Comment: {
// Auto-calculated: 325 * 10 = 3,250 comments
id: t.bigserial().primaryKey(),
articleId: t.integer(),
article: t.belongsTo('Article', 'articleId'),
content: t.text()
}
}
`$3
You can organize schemas in two ways:
Option 1: Single file with multiple tables
Define multiple related tables in a single file:
`typescript
// schemas/social.schema.ts
import { t } from '@doviui/dev-db'export default {
User: {
$count: 100,
id: t.bigserial().primaryKey(),
username: t.varchar(50).unique()
},
Post: {
$count: 500,
id: t.bigserial().primaryKey(),
user_id: t.foreignKey('User', 'id'),
title: t.varchar(200)
},
Comment: {
$count: 2000,
id: t.uuid().primaryKey(),
post_id: t.foreignKey('Post', 'id'),
user_id: t.foreignKey('User', 'id'),
content: t.text()
}
}
`Option 2: Multiple files in a directory
Organize each table in its own file for better maintainability:
`typescript
// schemas/users.ts
import { t } from '@doviui/dev-db'export default {
User: {
$count: 100,
id: t.bigserial().primaryKey(),
username: t.varchar(50).unique()
}
}
``typescript
// schemas/posts.ts
import { t } from '@doviui/dev-db'export default {
Post: {
$count: 500,
id: t.bigserial().primaryKey(),
user_id: t.foreignKey('User', 'id'),
title: t.varchar(200)
}
}
``typescript
// schemas/comments.ts
import { t } from '@doviui/dev-db'export default {
Comment: {
$count: 2000,
id: t.uuid().primaryKey(),
post_id: t.foreignKey('Post', 'id'),
user_id: t.foreignKey('User', 'id'),
content: t.text()
}
}
`Then generate with:
bunx @doviui/dev-db ./schemas$3
dev-db validates schemas before generation to catch errors early:
`typescript
// ❌ This will fail validation
export default {
Post: {
id: t.bigserial().primaryKey(),
user_id: t.foreignKey('User', 'id') // Error: User table doesn't exist!
}
}
`Output:
`
Schema validation failed: Post.user_id: Foreign key references non-existent table 'User'
`$3
Define type-safe JSON schemas for complex nested data structures. This generates proper TypeScript types instead of
any, and creates realistic structured data:`typescript
import { t } from '@doviui/dev-db'export default {
User: {
$count: 100,
id: t.bigserial().primaryKey(),
email: t.varchar(255).unique().notNull(),
// Structured JSON field with nested schema
profile: t.json({
firstName: t.varchar(50).generate('person.firstName'),
lastName: t.varchar(50).generate('person.lastName'),
age: t.integer().min(18).max(90),
// Deeply nested structures
preferences: t.json({
theme: t.varchar(20).enum(['light', 'dark', 'auto']).default('auto'),
notifications: t.json({
email: t.boolean().default(true),
push: t.boolean().default(false),
frequency: t.varchar(20).enum(['realtime', 'daily', 'weekly'])
})
})
}),
// Simple unstructured JSON (fallback)
metadata: t.jsonb()
}
}
`Generated TypeScript types:
`typescript
// mock-data/types.ts
export interface User {
id: number;
email: string;
profile: {
firstName: string;
lastName: string;
age: number;
preferences: {
theme?: string; // Optional because it has a default
notifications: {
email?: boolean;
push?: boolean;
frequency: string;
};
};
};
metadata: any; // Unstructured JSON falls back to 'any'
}
`Generated JSON data:
`json
{
"id": 1,
"email": "john@example.com",
"profile": {
"firstName": "John",
"lastName": "Doe",
"age": 34,
"preferences": {
"theme": "dark",
"notifications": {
"email": true,
"push": false,
"frequency": "daily"
}
}
},
"metadata": { "data": "sample" }
}
`Benefits:
- ✅ Full TypeScript type safety for nested JSON structures
- ✅ All field modifiers work (
.nullable(), .default(), .enum(), .generate(), etc.)
- ✅ Supports unlimited nesting depth
- ✅ Works with both t.json() and t.jsonb()$3
Leverage any Faker.js method for realistic data generation:
`typescript
t.varchar(100).generate('company.name')
t.varchar(50).generate('location.city')
t.integer().generate('number.int', { min: 1000, max: 9999 })
`Or write custom functions:
`typescript
t.varchar(20).generate(() => {
const colors = ['red', 'blue', 'green', 'yellow']
return colors[Math.floor(Math.random() * colors.length)]
})t.jsonb().generate((faker) => ({
preferences: {
theme: faker.helpers.arrayElement(['light', 'dark']),
language: faker.helpers.arrayElement(['en', 'es', 'fr'])
},
lastLogin: faker.date.recent().toISOString()
}))
`Command Line Interface
`bash
dev-db [options]Arguments:
Path to schema file or directory containing schema files
Options:
-o, --output
Output directory for generated JSON files (default: ./mock-data)
-s, --seed Random seed for reproducible data generation
-t, --types Generate TypeScript type definitions alongside JSON
-h, --help Show help message
`Examples:
`bash
Single file
bunx @doviui/dev-db schema.tsDirectory of schemas
bunx @doviui/dev-db ./schemasCustom output directory
bunx @doviui/dev-db ./schemas -o ./dataReproducible generation with seed
bunx @doviui/dev-db schema.ts -s 42Generate TypeScript types
bunx @doviui/dev-db schema.ts --typesAll options combined
bunx @doviui/dev-db ./schemas --output ./database --seed 12345 --types
`Schema File Format:
Schema files must export a default object or named
schema export:`typescript
import { t } from '@doviui/dev-db';export default {
User: {
$count: 100,
id: t.bigserial().primaryKey(),
email: t.varchar(255).unique().notNull().generate('internet.email'),
name: t.varchar(100).notNull()
}
};
`Directory Support:
When a directory is provided, all
.ts and .js files are loaded and their schemas are merged. This allows you to organize schemas across multiple files:`
schemas/
├── users.ts // exports { User: { ... } }
├── posts.ts // exports { Post: { ... } }
└── comments.ts // exports { Comment: { ... } }
`Running
bunx @doviui/dev-db ./schemas will merge all three schemas and generate data for all tables.$3
For more control, create a custom generation script:
`typescript
// scripts/generate-data.ts
import { MockDataGenerator, SchemaValidator } from '@doviui/dev-db'
import { schema } from './schemas/index'// Validate schema
const validator = new SchemaValidator()
const errors = validator.validate(schema)
if (errors.length > 0) {
console.error('Schema validation failed:')
errors.forEach(err => {
const location = err.field ?
${err.table}.${err.field} : err.table
console.error( ${location}: ${err.message})
})
process.exit(1)
}// Generate data
const generator = new MockDataGenerator(schema, {
outputDir: './mock-data',
seed: process.env.SEED ? parseInt(process.env.SEED) : undefined
})
await generator.generate()
console.log('Mock data generated successfully!')
`Add to package.json:
`json
{
"scripts": {
"generate:data": "bun run scripts/generate-data.ts"
}
}
`Then run with:
`bash
bun run generate:data
or with a custom seed
SEED=42 bun run generate:data
`Real-World Examples
$3
`typescript
// schemas/ecommerce.schema.ts
import { t } from '@doviui/dev-db'export default {
Customer: {
$count: 200,
id: t.uuid().primaryKey(),
email: t.varchar(255).unique().generate('internet.email'),
first_name: t.varchar(50).generate('person.firstName'),
last_name: t.varchar(50).generate('person.lastName'),
phone: t.varchar(20).generate('phone.number'),
created_at: t.timestamptz().default('now'),
// Each customer has 1-5 orders
orders: t.hasMany('Order', 'customerId', { min: 1, max: 5 })
},
Product: {
$count: 100,
id: t.bigserial().primaryKey(),
name: t.varchar(200).generate('commerce.productName'),
description: t.text().generate('commerce.productDescription'),
price: t.decimal(10, 2).min(5).max(5000),
stock: t.integer().min(0).max(1000),
category: t.varchar(50).enum(['electronics', 'clothing', 'home', 'books'])
},
Order: {
// Auto-calculated: 200 * 3 = 600 orders
id: t.bigserial().primaryKey(),
customerId: t.varchar(36),
customer: t.belongsTo('Customer', 'customerId'),
status: t.varchar(20).enum(['pending', 'processing', 'shipped', 'delivered']),
total: t.decimal(10, 2).min(10).max(10000),
created_at: t.timestamptz().default('now'),
// Each order has 1-4 items
items: t.hasMany('OrderItem', 'orderId', { min: 1, max: 4 })
},
OrderItem: {
// Auto-calculated: 600 * 2.5 = 1,500 items
id: t.bigserial().primaryKey(),
orderId: t.integer(),
order: t.belongsTo('Order', 'orderId'),
productId: t.integer(),
product: t.belongsTo('Product', 'productId'),
quantity: t.integer().min(1).max(10),
price: t.decimal(10, 2)
}
}
`$3
`typescript
// schemas/blog.schema.ts
import { t } from '@doviui/dev-db'export default {
Author: {
$count: 50,
id: t.uuid().primaryKey(),
username: t.varchar(50).unique().generate('internet.userName'),
email: t.varchar(255).unique().generate('internet.email'),
bio: t.text().generate('lorem.paragraph'),
avatar_url: t.varchar(500).generate('image.avatar'),
// Each author has 3-10 articles
articles: t.hasMany('Article', 'authorId', { min: 3, max: 10 })
},
Article: {
// Auto-calculated: 50 * 6.5 = 325 articles
id: t.bigserial().primaryKey(),
authorId: t.varchar(36),
author: t.belongsTo('Author', 'authorId'),
title: t.varchar(200).generate('lorem.sentence'),
slug: t.varchar(200).unique().generate('lorem.slug'),
content: t.text().generate('lorem.paragraphs', 5),
excerpt: t.varchar(500).generate('lorem.paragraph'),
published: t.boolean(),
published_at: t.timestamptz().nullable(),
created_at: t.timestamptz().default('now')
},
Tag: {
$count: 30,
id: t.serial().primaryKey(),
name: t.varchar(50).unique().generate('lorem.word')
},
ArticleTag: {
$count: 800,
articleId: t.integer(),
article: t.belongsTo('Article', 'articleId'),
tagId: t.integer(),
tag: t.belongsTo('Tag', 'tagId')
}
}
`Best Practices
$3
Define independent tables first - Structure your schemas so tables without foreign keys are defined before those with dependencies. This simplifies validation and generation.
Use realistic record counts - Set
$count values that reflect production ratios. For example, if users typically have 5 posts, generate 100 users and 500 posts.Leverage domain-specific generators - Use Faker.js methods that match your domain (e.g.,
company.name for business data, person.firstName for user data) to generate realistic datasets.$3
Validate frequently - Run generation regularly during schema development to catch errors early. The validator provides immediate feedback on structural issues.
Use seeds for reproducibility - In test environments and CI/CD, use the
--seed option to generate identical datasets across runs. This ensures consistent test results.Organize by domain - Group related tables in single schema files (e.g.,
auth.schema.ts, orders.schema.ts) for better maintainability and clearer relationships.Troubleshooting
$3
Problem:
Foreign key references non-existent table 'TableName'Solution: Ensure all referenced tables are defined in your schema before tables that reference them:
`typescript
// ✅ Correct - User defined before Post
export default {
User: {
id: t.bigserial().primaryKey(),
// ... other fields
},
Post: {
id: t.bigserial().primaryKey(),
user_id: t.foreignKey('User', 'id') // User exists in schema
}
}
`$3
Problem:
Could not generate unique value after 1000 attemptsSolution: This occurs when unique constraints are too restrictive for the number of records. Consider these options:
- Increase variety: Use a more diverse Faker.js generator that produces more unique values
- Reduce record count: Lower the
$count if you don't need that many records
- Remove constraint: If uniqueness isn't critical for your use case, remove the .unique() modifier$3
Problem:
Circular dependency detected involving table: TableNameSolution: dev-db cannot resolve circular foreign key relationships. Restructure your schema using one of these approaches:
- Make relationships optional: Use
.nullable()` on one of the foreign keysWe welcome contributions! Whether you're fixing bugs, improving documentation, or proposing new features, your input helps make dev-db better for everyone.
Please read our Contributing Guide to get started.
MIT License - see LICENSE for details
dev-db is built with:
- Faker.js - High-quality fake data generation
- Bun - Fast JavaScript runtime and toolkit