# SQLite with node:sqlite

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.

```ts title="src/db.ts"
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

```bash
deno run --allow-read=./data --allow-write=./data src/main.ts
```

- WAL mode creates `app.db-wal` and `app.db-shm` sidecar files. Granting write access to exactly `app.db` fails 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/sqlite` effectively need near `-A` trust; 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_timeout` controls how long lock contention waits before raising `SQLITE_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:sqlite` added `sqlite.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](https://docs.deno.com/runtime/reference/node_apis/), [Node.js node:sqlite docs](https://nodejs.org/api/sqlite.html), [jsr:@db/sqlite](https://jsr.io/@db/sqlite), [SQLite WAL mode](https://sqlite.org/wal.html).
