SQLite with node:sqlite
Using SQLite in Deno via node:sqlite and compatible libraries, with least-privilege and WAL/backup boundaries
Deno has shipped node:sqlite in its Node compatibility layer since v2.2, and the official Node API docs list it as fully supported. Upstream, Node added the module in v22.5.0 and—as of 2026-08-03—still labels it Stability 1.2 (release candidate), so minor API changes remain possible before full stabilization. Recheck after Deno upgrades.
import { DatabaseSync } from "node:sqlite";
const db = new DatabaseSync("./data/app.db");
db.exec(`
PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS posts (
id INTEGER PRIMARY KEY,
title TEXT NOT NULL
) STRICT;
`);
const insert = db.prepare("INSERT INTO posts (title) VALUES (?)");
insert.run("Hello Deno");
const rows = db.prepare("SELECT id, title FROM posts").all();
Every DatabaseSync API runs synchronously, which suits scripts, CLIs, and single-writer services. Do not put long synchronous queries on a high-concurrency request path.
Alternatives
| Library | Form | Notes |
|---|---|---|
node:sqlite | built-in Node compat module | no dependency to install; upstream still a release candidate |
jsr:@db/sqlite | FFI loading a prebuilt native library | its README requires --allow-ffi and --allow-env, plus network and file permissions to download and cache the native library |
npm:better-sqlite3 | Node native addon | relies on Deno's native addon support; verify against your Deno version |
FFI and native addons load machine code that JavaScript-level permissions cannot sandbox; count that in your trust boundary. Pure WASM options such as npm:sql.js avoid FFI, but the database lives in memory and persistence is your own problem.
When SQLite fits
- Single-node services, CLI tools, desktop or single-instance edge apps.
- Tests and local development: isolate each test with
:memory:or a temp file. - Read-heavy embedded workloads, with WAL to overlap reads and writes.
- Multi-region or edge replicas are the domain of LiteFS/libsql-style solutions, outside
node:sqlite's scope—evaluate them separately.
Least privilege
deno run --allow-read=./data --allow-write=./data src/main.ts
- WAL mode creates
app.db-walandapp.db-shmsidecar files. Granting write access to exactlyapp.dbfails at checkpoint or first write; grant the directory, or list all three files. - Read-only tools should open with
new DatabaseSync(path, { readOnly: true })and request only--allow-read. - FFI-based options like
jsr:@db/sqliteeffectively need near-Atrust; do not use narrow grants to reassure yourself.
WAL, backup, and concurrent-write boundaries
- SQLite has a single writer: WAL lets reads overlap with one write, but writes still serialize. Keep write transactions short;
busy_timeoutcontrols how long lock contention waits before raisingSQLITE_BUSY. - Multiple processes may open the same file, but write throughput does not scale. Never run SQLite on network filesystems such as NFS.
- Run
PRAGMA wal_checkpoint(TRUNCATE)before a file-copy backup, or the copy misses commits still in the WAL; alternatively use the official SQLite CLI.backup. - Upstream
node:sqliteaddedsqlite.backup()in Node v23.8.0 / v22.16.0; verify support on your Deno version before relying on it. - Treat migrations as a release step, as with PostgreSQL—schema changes at app startup multiply concurrency risk across instances.
Official references: Deno Node API support, Node.js node:sqlite docs, jsr:@db/sqlite, SQLite WAL mode.