SQLite
02 / 02

Advanced Features: JSON, FTS, WAL & Tooling

SQLite: JSON, Full-Text Search, WAL & Tooling

JSON Support

-- Store and query JSON (SQLite 3.38+ has json_* functions built-in)
CREATE TABLE products (
  id   INTEGER PRIMARY KEY,
  data TEXT  -- JSON stored as TEXT
);

INSERT INTO products (data) VALUES ('{"name": "Widget", "price": 9.99, "tags": ["sale", "new"]}');

-- Extract values
SELECT json_extract(data, '$.name') AS name,
       json_extract(data, '$.price') AS price
FROM products;

-- Filter by JSON field
SELECT * FROM products
WHERE json_extract(data, '$.price') < 20;

-- JSON array functions
SELECT json_each.value FROM products, json_each(data, '$.tags')
WHERE products.id = 1;

-- Update JSON field
UPDATE products
SET data = json_set(data, '$.price', 7.99)
WHERE id = 1;

Full-Text Search (FTS5)

-- FTS5 virtual table for full-text search
CREATE VIRTUAL TABLE docs_fts USING fts5(
  title, body,
  content='docs',        -- external content table
  content_rowid='id'
);

-- Populate FTS index
INSERT INTO docs_fts(rowid, title, body)
  SELECT id, title, body FROM docs;

-- Search
SELECT rowid, title, rank
FROM docs_fts
WHERE docs_fts MATCH 'sqlite AND performance'
ORDER BY rank;

-- Highlight matches
SELECT highlight(docs_fts, 1, '<b>', '</b>') AS body_highlighted
FROM docs_fts
WHERE docs_fts MATCH 'sqlite';

-- Snippet
SELECT snippet(docs_fts, 1, '<b>', '</b>', '...', 10) AS excerpt
FROM docs_fts WHERE docs_fts MATCH 'performance';

WAL Mode & Concurrency

WAL (Write-Ahead Logging) is the recommended journal mode for most applications. It allows concurrent reads during a write — a major improvement over the default DELETE journal mode.

  • WAL: readers never block writers; writers never block readers — only one writer at a time

  • Default (DELETE): writer blocks all readers and writers

  • WAL checkpoint: WAL file is periodically merged back into the main DB file (auto or manual)

  • WAL creates two extra files: db.db-wal and db.db-shm — keep them together with the .db file

  • SQLite on NFS/network shares: avoid WAL — use DELETE journal mode

  • Connection timeout: set busy_timeout pragma to retry instead of immediately erroring

PRAGMA journal_mode = WAL;
PRAGMA busy_timeout = 5000;   -- wait up to 5s if DB is locked (instead of SQLITE_BUSY error)

-- Manual WAL checkpoint
PRAGMA wal_checkpoint(TRUNCATE);

Using SQLite in Node.js

// better-sqlite3 (synchronous, fastest)
import Database from 'better-sqlite3'

const db = new Database('app.db')
db.pragma('journal_mode = WAL')
db.pragma('foreign_keys = ON')

// Prepared statements (auto-cached)
const getUser = db.prepare('SELECT * FROM users WHERE id = ?')
const user = getUser.get(1)

// Transactions (synchronous)
const insertMany = db.transaction((users) => {
  for (const user of users) {
    db.prepare('INSERT INTO users (email) VALUES (?)').run(user.email)
  }
})
insertMany(usersArray)

// Drizzle ORM with SQLite
import { drizzle } from 'drizzle-orm/better-sqlite3'
const drizzleDb = drizzle(db)

// Turso (SQLite at the edge — libSQL protocol)
import { createClient } from '@libsql/client'
const turso = createClient({ url: process.env.TURSO_URL!, authToken: process.env.TURSO_TOKEN })
await turso.execute('SELECT * FROM users WHERE id = ?', [1])

Tooling

  • DB Browser for SQLite (sqlitebrowser): free GUI for browsing and editing .db files

  • sqlite3 CLI: built into macOS/Linux — sqlite3 app.db ".tables"

  • Drizzle ORM: first-class SQLite support with better-sqlite3 or libSQL

  • Prisma: SQLite adapter — good for local dev, limited for production

  • Turso: SQLite-compatible cloud DB with global replication (libSQL fork)

  • Cloudflare D1: serverless SQLite — 5GB free, runs at edge workers

  • LiteFS: SQLite replication across multiple nodes via FUSE filesystem

Keep your own version of these notes — editable, searchable, and organised by your stack.

Start free