SQLite
UQL speaks SQLite through three drivers. All of them produce identical SQL and identical results; they differ only in what you have to install and how fast they read.
| Pool | Driver | Install |
|---|---|---|
NodeSqliteQuerierPool |
Node’s built-in node:sqlite |
nothing |
Sqlite3QuerierPool |
better-sqlite3, or bun:sqlite under Bun |
npm i better-sqlite3 (Bun needs nothing) |
HranaQuerierPool |
libSQL / Turso over the wire | see Turso |
No dependency at all
Section titled “No dependency at all”NodeSqliteQuerierPool uses the SQLite that ships inside Node, so there is no native module to build
and nothing to install:
import { NodeSqliteQuerierPool } from 'uql-orm/sqlite';
export const pool = new NodeSqliteQuerierPool('app.db');That matters most where a native build is awkward: slim container images, CI without a toolchain, and
anywhere node-gyp is unwelcome. UQL requires Node 24, which is well past the 22.13 where node:sqlite
became usable, so there is no version to check.
Loadable extensions work too, which is what vector search needs, since SQLite ships no vector functions of its own:
import { getLoadablePath } from 'sqlite-vec';
const pool = new NodeSqliteQuerierPool('app.db', { extensions: [getLoadablePath()] });When to prefer better-sqlite3
Section titled “When to prefer better-sqlite3”better-sqlite3 is faster on reads. Measured on 20k in-memory rows, node:sqlite was about 20% quicker
on inserts but 1.3x slower on point reads and 1.4x slower on 100-row reads. Reads dominate most
workloads, so better-sqlite3 stays the recommendation when throughput matters and installing a native
module is not a problem.
node:sqlite is also still a release candidate in Node’s own stability index, while better-sqlite3 is
long settled.
import { Sqlite3QuerierPool } from 'uql-orm/sqlite';
export const pool = new Sqlite3QuerierPool('app.db');Under Bun this same pool uses bun:sqlite automatically, so Bun projects install nothing either.
Releasing the connection
Section titled “Releasing the connection”Every querier releases itself when an await using binding goes out of scope, however the block exits:
await using querier = await pool.getQuerier();const users = await querier.findMany(User, {});