DEV Community

Cover image for Seed a PostgreSQL database with realistic, connected fake data in one command
Hamza Mansoor
Hamza Mansoor

Posted on

Seed a PostgreSQL database with realistic, connected fake data in one command

Every time I start or test a project, I hit the same wall: the app works, but the database is empty. No customers, no orders, nothing to click through. So I write a seed script. Then a slightly different one on the next project. And the one after that.

Faker solves half of it — it gives you names and emails. The annoying half is keeping the data consistent: an order has to reference a customer that actually exists, a unique column has to stay unique, an enum can only hold its allowed values. Get that wrong and your app breaks on its own fake data.

So I built fauxdata and open-sourced it. Point it at a PostgreSQL database and it fills every table with realistic data in one command:

npx fauxdata --dsn postgres://localhost/mydb
Enter fullscreen mode Exit fullscreen mode

No config. It reads your existing schema and figures out the rest.

fauxdata terminal demo

Try it in two minutes

Spin up a throwaway Postgres:

docker run -d --name shop -e POSTGRES_PASSWORD=postgres -p 5432:5432 postgres:16
Enter fullscreen mode Exit fullscreen mode

Give it a schema with the tricky bits — a foreign key, a unique column, and an enum:

CREATE TYPE order_status AS ENUM ('pending', 'paid', 'shipped', 'cancelled');

CREATE TABLE customers (
  id         serial PRIMARY KEY,
  email      varchar(255) NOT NULL UNIQUE,
  full_name  text NOT NULL
);

CREATE TABLE orders (
  id           serial PRIMARY KEY,
  customer_id  integer NOT NULL REFERENCES customers (id),
  status       order_status NOT NULL,
  total        numeric(10, 2) NOT NULL
);
Enter fullscreen mode Exit fullscreen mode

Seed it:

npx fauxdata --dsn postgres://postgres:postgres@localhost:5432/postgres --rows 20
# fauxdata: inserted 40 rows across 2 tables.
Enter fullscreen mode Exit fullscreen mode

Now the data actually joins:

select c.full_name, o.total, o.status
from orders o
join customers c on c.id = o.customer_id
limit 3;
Enter fullscreen mode Exit fullscreen mode
   full_name    |  total  |  status
----------------+---------+----------
 Andrea Gerlach | 7891.93 | shipped
 Pauline Larkin | 5868.83 | paid
 Leonard Rempel | 2210.50 | pending
Enter fullscreen mode Exit fullscreen mode

Every customer_id points to a real customer. Every status is a valid enum. Every email is unique.

What it handles

  • Referential integrity — it reads your foreign keys and inserts parents before children, so references always resolve.
  • The awkward cases — self-referencing tables (an employee whose manager is another employee) and circular references between tables, handled with a two-pass insert.
  • ConstraintsNOT NULL, unique columns, enums, and composite primary keys.
  • Reproducibility — pass --seed 42 and you get byte-identical data every run, which makes it reliable in CI and shareable across a team.
  • Insert or inspect — write straight to the database, or emit a SQL file you can review first:
npx fauxdata --dsn postgres://localhost/mydb --output seed.sql
Enter fullscreen mode Exit fullscreen mode

How it works

Under the hood it's TypeScript on Node, and the pipeline is small on purpose:

  1. Introspect — read tables, columns, types, keys, uniques, foreign keys, and enums straight from information_schema / pg_catalog.
  2. Order — build a dependency graph from the foreign keys and topologically sort it, deferring the minimum set of edges needed to break cycles and self-references.
  3. Generate — pick a value per column: a config override if you gave one, then a column-name heuristic (email, *_at, price…), then a fallback by SQL type. Uniqueness and enums are enforced as it goes.
  4. Resolve & write — fill foreign keys from already-inserted parent rows, then insert in one transaction (or write the SQL file).

node-postgres does the talking; Faker provides the values.

Why PostgreSQL first

Because I'd rather do one database really well than five halfway. Postgres is where most of us start, so it's where fauxdata starts. MySQL and SQLite are the next targets — and since the generator works on an abstract schema model, adding them is mostly a new reader, not a rewrite.

Try it / break it

It's MIT-licensed, needs no account, and installs with npx.

If you run it against a real-world schema, I'd like to know what breaks — odd types, exotic constraints, anything. Issues and PRs welcome.

Top comments (0)