Own Your Stack.

Own Your Stack/The Foundation/pgflex

Own your database

pgflex

One Postgres API, two modes — real PostgreSQL in production, in-process PGlite with no server.

github → MIT the foundation postgres

01What it is

Most apps need Postgres; most dev and CI environments don't want to run one. So you end up maintaining two versions of the truth — a real server for production, and some lighter, lesser thing for everything else — and the gap between them is where the surprises live.

pgflex is one DatabaseAdapter interface over two backends. Production runs on a real PostgreSQL server through pg. Dev, CI, and standalone installs run in-process on PGlite — full PostgreSQL compiled to WASM, in your Node process, with no server, no port, no Docker. The mode flips on one line of config or an env var, and your SQL, transactions, and codepaths stay identical between the two.

pgflex · two modes
// production — real Postgres server
const db = await createAdapter({
  mode: 'pg',
  connectionString: process.env.DATABASE_URL,
});

// dev / standalone — PGlite, no server
const db = await createAdapter({
  mode: 'pglite',
  dataDir: '~/.myapp/data',
});
Fig. 1 — same interface, the mode is the only thing that changes.

02What it does

One interface, two backends

A single DatabaseAdapterquery, queryOne, exec, transaction, listen, notify, ping, close — backed by either real Postgres or PGlite. The same SQL, query shape, and $1 parameter style run unchanged in both. db.mode is exposed if a piece of your app needs to branch, but most don't.

Drop the server when you don't need it

In pglite mode you get full PostgreSQL — JSONB, ON CONFLICT, RETURNING, triggers, functions, even pgvector — running in WASM inside your Node process. No port, no Docker, no service to start. Point dataDir at a path to persist, or memory:// for an ephemeral store.

Transactions and LISTEN/NOTIFY that match

transaction() auto-commits on return and rolls back on throw, with the same isolation expectations in both modes. listen() / notify() share one API: in pg mode notifications arrive on a dedicated connection that auto-reconnects and re-LISTENs on drop; in pglite mode it delegates to PGlite's native listen().

The same migrations run in both modes

A tiny, dependency-free runner with deliberately boring conventions: plain .sql files applied in filename order, tracked in a pgflex_migrations table. Each file runs in its own transaction with its tracking row, so a failed migration rolls back completely and isn't recorded. Because it runs on the adapter interface, the same files set up real Postgres in production and PGlite in dev and CI.

A thin adapter, with escape hatches

Not an ORM, not a pooler, not magic — bring your own query builder. PGlite ships in optionalDependencies, so a pure pg install can --omit=optional and skip the WASM bytes. pg mode exposes pool tuning and a getPool() escape hatch for COPY and cursors; MIT-licensed throughout.


03Where it sits

Part of The Foundation.

pgflex is the database layer. The Foundation is the datastores and drivers underneath — built to run with a server in production or in-process with none, the same interface either way.

Own your database.

pgflex is open source and MIT-licensed. Read the code, run it on your own box, drop the server when you don't need it.

View pgflex on GitHub →