Skip to content

Node — node:sqlite

Node ships a built-in SQLite module, node:sqlite, since Node 22.5 (still flagged experimental, but usable — it prints an ExperimentalWarning). No native dependency to install.

Enabling extension loading

node:sqlite gates extension loading twice — you must opt in when constructing the database and enable the loader:

ts
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync("app.db", { allowExtension: true });  // 1: opt in
db.enableLoadExtension(true);                                     // 2: enable
db.loadExtension("./dist/rls.so");
db.enableLoadExtension(false);                                    // re-disable

Full example

ts
import { DatabaseSync } from "node:sqlite";

const db = new DatabaseSync("app.db", { allowExtension: true });
db.enableLoadExtension(true);
db.loadExtension("./dist/rls.so");
db.enableLoadExtension(false);

db.exec(`
  CREATE TABLE documents(id INTEGER PRIMARY KEY, owner_id INT NOT NULL, title TEXT);
  INSERT INTO documents(owner_id, title) VALUES (1,'Alice'), (2,'Bob');
`);

db.prepare("SELECT rls_protect(?)").get(
  JSON.stringify({ table: "documents", select: "owner_id = rls_ctx('user_id')" }));
db.prepare("SELECT rls_seal(?)").get(process.env.RLS_TOKEN);
db.prepare("SELECT rls_guard()").get();

// per query:
db.prepare("SELECT rls_set_context(?)").get(JSON.stringify({ user_id: 1 }));
db.prepare("SELECT title FROM documents").all();   // [{ title: 'Alice' }]

The API surface (prepare, get, all, run, exec) is nearly identical to better-sqlite3, so patterns from that guide — write results reporting changes = 0, using rls_last_rowid(), connection-pool split — carry over unchanged.

Using the shim

The RlsConnection shim is typed against better-sqlite3's interface, but the shape it needs (loadExtension, prepare().run/get/all) matches node:sqlite too. If you prefer the built-in driver, a thin adapter around DatabaseSync works; the extension itself behaves identically either way.

Notes

  • Keep allowExtension: true and the enableLoadExtension(true/false) bracket tight — enable only long enough to load rls.so, then disable, so application SQL can't load anything else. (The authorizer also blocks the load_extension() SQL function once guarded.)
  • The experimental warning is Node's, not the extension's; silence it with --no-warnings=ExperimentalWarning if it's noisy in logs.

A loadable SQLite extension. No warranty; test against your own schema.