Prerequisites

A running server (--listen 127.0.0.1:5433 --db file://./srv.db). On Node: npm i pg. On Bun: nothing — Bun.sql is built in.

Bun — Bun.sql

import { SQL } from "bun";

const sql = new SQL("postgresql://[email protected]:5433/main?sslmode=disable");

// Tagged-template values are sent as bind parameters (extended protocol).
const rows = await sql`SELECT id, body FROM notes ORDER BY id DESC LIMIT 50`;

await sql`INSERT INTO notes (body, ts) VALUES (${"hello"}, ${new Date().toISOString()})`;

Breakdown: the ${...} interpolations are not string concatenation — Bun.sql turns them into Parse/Bind parameters, which the server's extended protocol handles. That's both safe (no injection) and what lets the engine plan the statement.

Node — node-postgres (pg)

import { Client } from "pg";

const client = new Client({
  host: "127.0.0.1",
  port: 5433,
  user: "postgres",
  database: "main",
  ssl: false, // cleartext — the listener does not terminate TLS
});

await client.connect();

const { rows } = await client.query(
  "SELECT id, body FROM notes WHERE id = $1",
  [1],
);

await client.query(
  "INSERT INTO notes (body, ts) VALUES ($1, $2)",
  ["hello", new Date().toISOString()],
);

await client.end();

Breakdown: node-postgres uses $1, $2… numbered placeholders bound from the array — the server supports this placeholder form. ssl: false matches the cleartext listener.

Transactions

// node-postgres
await client.query("BEGIN");
try {
  await client.query("UPDATE accounts SET balance = balance - $1 WHERE id = $2", [100, 1]);
  await client.query("UPDATE accounts SET balance = balance + $1 WHERE id = $2", [100, 2]);
  await client.query("COMMIT"); // returns once the engine's WAL is durable
} catch (e) {
  await client.query("ROLLBACK");
  throw e;
}

Breakdown: the engine runs real transactions; COMMIT blocks until durable. A 40001-class serialization/conflict error (another writer committed first) is safe to retry — re-run the whole transaction.

Pooling

For a web app, use pg.Pool (Node) or rely on Bun.sql's built-in pool. Because the engine serializes writers, place a transaction-mode pooler in front for serverless bursts — see Connection pooling.

import { Pool } from "pg";
const pool = new Pool({ host: "127.0.0.1", port: 5433, user: "postgres", database: "main", ssl: false, max: 10 });
const { rows } = await pool.query("SELECT count(*) FROM notes");

Next

Twill DB documentation · Licensed under BUSL-1.1. · Author