PostgreSQL
PostgreSQL is UQL’s most complete backend, with native arrays, JSON operators, full-text and pgvector semantic search, and RETURNING for generated ids.
Install and connect
Section titled “Install and connect”npm install uql-orm pgimport { PgQuerierPool } from 'uql-orm/postgres';
export const pool = new PgQuerierPool({ connectionString: process.env.DATABASE_URL, max: 10,});The pool takes node-postgres’ PoolConfig, with keepAlive: true by default. See Pool for sizing and shutdown, and PGlite for the same dialect in-process, for tests.
Streaming also needs npm install pg-query-stream, which UQL loads on first use.
Transactions
Section titled “Transactions”All four isolation levels work. serializable and repeatable read can fail with 40001; retry the whole transaction, never a statement inside it.
await pool.transaction(run, { isolationLevel: 'serializable' });Behind PgBouncer or another transaction-mode pooler, session state does not carry between statements, so keep SET LOCAL, advisory locks and temp tables inside one transaction callback.
Row-level security
Section titled “Row-level security”RLS backs up UQL’s security filters and also covers raw SQL. Set the tenant inside the transaction:
await pool.transaction(async (querier) => { await querier.run`SELECT set_config('app.tenant_id', ${tenantId}, true)`; return querier.findMany(Invoice, { $limit: 50 });});Cloudflare Hyperdrive
Section titled “Cloudflare Hyperdrive”Hyperdrive keeps a warm Postgres pool at the edge for Workers, and pg runs on it unchanged. It needs nodejs_compat and a compatibility date of 2024-09-23 or later:
{ "compatibility_date": "2026-08-01", "compatibility_flags": ["nodejs_compat"], "hyperdrive": [{ "binding": "HYPERDRIVE", "id": "<id>" }],}The binding exists only inside a request, so build the pool there, with a small max since Hyperdrive does the real pooling:
import { PgQuerierPool } from 'uql-orm/postgres';import { Item } from './models';
export default { async fetch(request: Request, env: Env, ctx: ExecutionContext) { const pool = new PgQuerierPool({ connectionString: env.HYPERDRIVE.connectionString, max: 5, }); try { return Response.json(await pool.findMany(Item, { $limit: 20 })); } finally { ctx.waitUntil(pool.end()); } },};Run migrations from CI against the database’s own hostname, not through the binding.
Related pages
Section titled “Related pages”Bun SQL drops pg under Bun. See also CockroachDB, Supabase, Neon and Serverless.