DataForge
A browser-based tool for generating related e-commerce seed data as JSON, CSV or SQL.
Overview
I initially built DataForge as a side project to supply realistic seed data for my full-stack e-commerce application. It grew into a standalone browser tool for configuring product catalogues, generating related customers, orders and reviews, and exporting them without a backend or account.
- context
- Personal project
- role
- Solo
- started
- Feb 2026
- setting
- Client-side seed-data tool with a public GitHub Pages demo; configuration is stored locally in the browser.
- TypeScript 5.9
- React 19.2
- Zustand 5
- Faker 9
- Vite 7.3
- Tailwind CSS 4
- +1
Screenshots
Select an image to inspect the full-resolution screenshot.
Key decisions
- Generate related rows from one canonical dataset
- Products and customers are generated before orders, order lines and reviews, so dependent rows can reference existing IDs.
- Rename fields at the output boundary
- A shared mapping transform feeds the preview, clipboard and exporters without mutating the generated rows.
- Separate generation logic from the interface
- Core generation, validation and serialization modules are independent of React and the browser adapters.
Demonstrated results
- Relationships and totals are covered by tests
- Dataset tests check references across all five tables, order totals, decimal precision and repeatability with a fixed seed and clock.
- Mapping behaviour is shared across output formats
- Serialization tests cover mapped JSON keys, flattened CSV columns, PostgreSQL and MySQL column identifiers, identity output and source immutability.
Known limitations
- A seed does not freeze the clock
- Timestamp fields depend on generation time. Exact repeatability requires the same configuration, seed and time input, not the seed alone.
- SQL output assumes an existing schema
- The exporter creates INSERT statements with canonical table names. It does not create tables or adapt every database constraint automatically.
- Generated data stays in browser memory
- Configuration persists, but the dataset does not. Reloading requires regeneration, and large datasets remain subject to browser resource limits.
From e-commerce fixture to standalone tool
The original use case came from my full-stack e-commerce project. A storefront needs more than unrelated random product rows: customers place orders, order lines refer to products, and reviews belong to both a product and a customer. I wanted configurable data that I could regenerate and export for development.
The repository starts in February 2026. Later work extracted the generation logic, added related tables and export formats, and introduced output field mapping. The current interface exposes six category templates, editable brands and models, weighted brand selection, price overrides and typed attributes. Presets package the configuration so it can be reused rather than rebuilt by hand.
Related data before export
generateDataset() builds products and customers first. The order generator then
selects from those existing rows, and review generation receives the same product
and customer collections. The returned dataset has five tables: products,
customers, orders, order_lines and reviews.
The distinction matters at export time. An order line carries a product ID and an order ID, not independently generated lookalikes. Dataset tests check that each reference exists and that each order total matches the sum of its line totals. Additional cases check cent precision and empty related datasets.
These are generation and unit-test guarantees in the reviewed source, rather than evidence that every export has been imported into every possible target schema. The dataset tests make the checked relationships explicit.
Field mapping without changing the dataset
A consumer may need w_kg rather than weight_kg, or camelCase instead of
snake_case. Changing the internal row types for each consumer would couple
generation to export preferences. DataForge instead retains canonical rows and
applies an output mapping immediately before serialization.
The same mapping-aware wrappers serve preview, clipboard, JSON, CSV and SQL.
Validation rejects invalid identifiers, duplicate names and collisions, including
the attr_* columns produced when CSV flattens product attributes. Invalid mappings
block data copy and download until corrected. Table names stay canonical, and the
attributes container is fixed.
The serialization tests check each format, unchanged output under an identity mapping, and that the transformation does not mutate source rows. Preset version 2 carries the mapping; older version 1 presets migrate with an identity mapping.
Repeatability and the clock
The random-number wrapper owns a seeded Faker instance. Reusing the seed and
configuration reproduces catalogue choices and relationships, but timestamps use
a separate now input that defaults to the current time.
The complete dataset test therefore fixes all three inputs:
generateDataset(category, brands, selectedBrandIds, config, new SeededRng(42), now);
This is useful for fixtures whose exact values matter. In the browser, running the same configuration on another day should not be described as byte-for-byte reproduction of every timestamp.
Export boundaries
JSON can contain the full dataset; CSV exports the active table and flattens product attributes into columns. SQL writes the full dataset in dependency order: products, customers, orders, order lines, then reviews.
The SQL exporter
quotes identifiers for PostgreSQL or MySQL and emits INSERT statements. It does
not generate DDL. The receiving database needs compatible tables, columns and
constraints, and output field mappings need to match that schema.
A browser tool with a testable core
The core modules contain generation, normalization, field validation, presets and serialization. React components use a Zustand store, while small browser adapters handle downloads, theme and panel sizing. This lets the test suite exercise data behaviour without rendering the entire interface.
Configuration persists in localStorage, including output mappings. The generated dataset is deliberately excluded from persisted state. Reloading returns to the configuration and preview flow instead of restoring old generated rows.
All generation happens locally in memory. There is no backend or account, and no server-side job queue for large datasets. The repository's tests focus on logic and helpers; they should not be presented as a full end-to-end browser test suite or a benchmark of large-dataset performance.
The public architecture notes describe the module boundaries, persistence keys and deployment pipeline.
Stack and source figures
how it fits together
- generation
-
-
Faker + TypeScriptA seeded Faker wrapper supplies random values to declarative catalogue templates and the related-row generators.
-
- interface and state
-
-
React + ZustandCatalogue editors, dataset tabs and field mapping share a store; configuration persists in localStorage while generated rows stay in memory. -
Tailwind CSSLight and dark themes, a resizable desktop split and a configuration drawer for smaller screens.
-
- verification and delivery
-
-
VitestTests generation, output formats, field validation, presets, persistence migrations and browser-adapter helpers. -
Vite + GitHub ActionsBuilds a static application; CI gates deployment to GitHub Pages on lint, typecheck, tests and build.
-