skills/sqlite/SKILL.md
Expert SQLite guidance for modern full-stack and AI projects. Use when user mentions sqlite, sqlite3, bun:sqlite, better-sqlite3, aiosqlite, libsql, Turso, Cloudflare D1, WAL mode, FTS5, sqlite-vec, or asks about local/embedded databases, schema design, SQLite migrations with Drizzle or raw SQL, performance tuning, backup/restore, and building stateful CLI with SQLite.
npx skillsauth add fatih-developer/fth-skills sqliteInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
SQLite is a self-contained, serverless, zero-configuration SQL database engine. It's the right choice for local tools, CLI apps, AI agents, edge deployments (Cloudflare D1, Turso), and embedded databases in SaaS products.
What do you need?
├── TypeScript + Bun project? → references/TYPESCRIPT_DRIZZLE.md
├── Python / FastAPI project? → references/PYTHON.md
├── Performance, WAL, PRAGMA tuning? → references/PERFORMANCE.md
├── Full-text search (FTS5)? → references/FTS5_VECTOR.md §FTS5
├── AI vector embeddings? → references/FTS5_VECTOR.md §sqlite-vec
├── Cloud SQLite (Turso / D1)? → references/CLOUD.md
├── Building a stateful CLI Skill? → references/CLI_SKILL_PATTERN.md
├── Schema design & normalization? → This file §Schema Design
└── Backup, vacuum, integrity? → This file §Maintenance
Read the referenced file ONLY when relevant. Don't load all references at once.
| Runtime | Recommended Driver | Notes |
|---|---|---|
| Bun | bun:sqlite (built-in) | Zero-dep, fastest |
| Bun + ORM | drizzle-orm/bun-sqlite | Type-safe, migrations |
| Node.js | better-sqlite3 | Sync, very fast |
| Node.js async | @libsql/client | For Turso/libsql |
| Python sync | sqlite3 (stdlib) | Zero-dep |
| Python async | aiosqlite | asyncio-native |
| Edge / CF | Cloudflare D1 binding | Workers only |
Run these immediately after opening any connection:
PRAGMA journal_mode = WAL; -- Concurrent readers + writer
PRAGMA synchronous = NORMAL; -- Safe + fast (vs FULL)
PRAGMA foreign_keys = ON; -- Enforce FK constraints
PRAGMA cache_size = -64000; -- 64 MB page cache
PRAGMA temp_store = MEMORY; -- Temp tables in RAM
PRAGMA mmap_size = 268435456; -- 256 MB memory-mapped I/O
⚠️
WALis a file-level setting — it persists across connections. Set once, check after.
-- Tables: snake_case, plural
CREATE TABLE user_sessions (...);
-- Columns: snake_case
created_at, updated_at, deleted_at
-- PKs: always integer with AUTOINCREMENT or WITHOUT ROWID
id INTEGER PRIMARY KEY AUTOINCREMENT
-- FKs: <table_singular>_id
user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE
SQLite has flexible typing — use these mappings:
| Concept | SQLite Type | Notes |
|---|---|---|
| Auto-increment PK | INTEGER PRIMARY KEY | Also the rowid alias |
| String | TEXT | UTF-8 by default |
| Boolean | INTEGER (0/1) | No native bool |
| Float | REAL | 8-byte IEEE 754 |
| Timestamps | INTEGER (unix) or TEXT (ISO8601) | Pick one, be consistent |
| JSON | TEXT | Use json_extract() / -> operator |
| Binary | BLOB | Images, embeddings |
| UUID | TEXT (36 chars) or BLOB (16 bytes) | BLOB is 60% smaller |
created_at INTEGER NOT NULL DEFAULT (unixepoch()),
updated_at INTEGER NOT NULL DEFAULT (unixepoch())
Use triggers for updated_at:
CREATE TRIGGER set_updated_at
AFTER UPDATE ON my_table
BEGIN
UPDATE my_table SET updated_at = unixepoch() WHERE id = NEW.id;
END;
SQLite 3.38+ supports the -> and ->> operators:
-- Store
INSERT INTO events (payload) VALUES ('{"type":"click","x":100}');
-- Extract (returns JSON)
SELECT payload -> '$.type' FROM events;
-- Extract as text (no quotes)
SELECT payload ->> '$.type' FROM events;
-- Index a JSON field
CREATE INDEX idx_events_type ON events(payload ->> '$.type');
deleted_at INTEGER DEFAULT NULL -- NULL = alive, unix timestamp = deleted
-- Query living rows
SELECT * FROM users WHERE deleted_at IS NULL;
-- Partial index for performance
CREATE INDEX idx_users_active ON users(email) WHERE deleted_at IS NULL;
-- Single column
CREATE INDEX idx_posts_user_id ON posts(user_id);
-- Composite — order matters: equality first, range last
CREATE INDEX idx_sessions_user_created ON sessions(user_id, created_at);
-- Partial index — only index relevant rows
CREATE INDEX idx_jobs_pending ON jobs(created_at) WHERE status = 'pending';
-- Covering index — avoids table lookup entirely
CREATE INDEX idx_users_email_name ON users(email, name);
-- Inspect query plan
EXPLAIN QUERY PLAN SELECT * FROM posts WHERE user_id = 1 ORDER BY created_at DESC;
Index anti-patterns:
(a, b) helps WHERE a=? AND b=? and WHERE a=?, but NOT WHERE b=?-- Explicit transaction (fastest for bulk inserts)
BEGIN;
INSERT INTO logs VALUES (...);
INSERT INTO logs VALUES (...);
COMMIT;
-- Savepoints (nested transactions)
SAVEPOINT sp1;
-- ... operations ...
ROLLBACK TO sp1; -- or RELEASE sp1 to commit
-- Immediate write lock (prevents SQLITE_BUSY in WAL mode)
BEGIN IMMEDIATE;
Bulk insert pattern (10-100x faster than individual inserts):
// Bun / better-sqlite3
const insert = db.prepare('INSERT INTO items (name, value) VALUES (?, ?)');
const insertMany = db.transaction((items) => {
for (const item of items) insert.run(item.name, item.value);
});
insertMany(myArray); // Single transaction
-- Reclaim space after large deletes (creates new file, takes time)
VACUUM;
-- Incremental vacuum (WAL mode, run periodically)
PRAGMA incremental_vacuum(100); -- Free 100 pages
-- Update query planner statistics
ANALYZE;
-- Integrity check
PRAGMA integrity_check;
-- Quick check (faster, less thorough)
PRAGMA quick_check;
# CLI backup (safe while DB is in use)
sqlite3 app.db ".backup backup_$(date +%Y%m%d).db"
# Hot backup via SQLite Online Backup API
sqlite3 app.db "VACUUM INTO 'backup.db';"
# Python: safe online backup
import sqlite3
src = sqlite3.connect('app.db')
dst = sqlite3.connect('backup.db')
src.backup(dst, pages=100) # 100 pages at a time = non-blocking
dst.close(); src.close()
SELECT
page_count * page_size AS total_bytes,
freelist_count * page_size AS free_bytes,
ROUND(freelist_count * 100.0 / page_count, 1) AS fragmentation_pct
FROM pragma_page_count(), pragma_page_size(), pragma_freelist_count();
| Pitfall | Fix |
|---|---|
| SQLITE_BUSY errors | Enable WAL mode; use BEGIN IMMEDIATE for writes |
| Slow bulk inserts | Wrap in explicit BEGIN/COMMIT transaction |
| No FK enforcement | Run PRAGMA foreign_keys = ON on every connection |
| Boolean confusion | Store as INTEGER 0/1; never 'true'/'false' |
| Timestamp inconsistency | Pick unix (INTEGER) or ISO8601 (TEXT), never mix |
| File locking on network drives | Never use SQLite on NFS/SMB — copy locally first |
| WAL file growing | Run PRAGMA wal_checkpoint(TRUNCATE) periodically |
| File | When to Read |
|---|---|
| references/TYPESCRIPT_DRIZZLE.md | TypeScript + Bun + Drizzle ORM setup, migrations, relations |
| references/PYTHON.md | Python sqlite3, aiosqlite, FastAPI integration |
| references/PERFORMANCE.md | Deep PRAGMA tuning, profiling, EXPLAIN QUERY PLAN |
| references/FTS5_VECTOR.md | FTS5 full-text search + sqlite-vec AI embeddings |
| references/CLOUD.md | Turso (libsql), Cloudflare D1, multi-region |
| references/CLI_SKILL_PATTERN.md | CLI + SQLite stateful Skill pattern |
tools
Create, optimize, critique, and structure prompts for AI systems. Use this skill whenever the user is designing or improving a prompt, system prompt, coding prompt, image prompt, evaluation rubric, agent prompt, workflow prompt, or MCP-oriented prompt package. Also use it when the user asks to turn vague AI behavior into a precise instruction set, tool policy, agent spec, or prompt architecture.
testing
Assumption-first architecture review skill to stress-test project plans and expose hidden risks.
testing
Enforce and manage DESIGN.md specifications, extract design systems from URLs, and combine design reasoning with token roles to prevent drift.
testing
Forces the agent to act with a Claude-like product mindset, prioritizing user journey, UX states, and visual quality before coding.