Introduction
better-sqlite3 is a Node.js binding for SQLite that uses a synchronous API. Unlike callback or promise-based SQLite libraries, better-sqlite3 executes queries on the main thread and returns results immediately. This design is faster for SQLite because the database engine itself is synchronous, and wrapping it in async layers adds unnecessary overhead. The library is the most widely used SQLite binding in the Node.js ecosystem.
What better-sqlite3 Does
- Executes SQL queries synchronously with prepare/run/get/all methods
- Supports transactions with automatic commit/rollback via the transaction() helper
- Allows user-defined functions, aggregate functions, and virtual tables
- Provides WAL mode for concurrent read access from multiple processes
- Handles large datasets efficiently with iterate() for row-by-row streaming
Architecture Overview
better-sqlite3 is a native Node.js addon written in C++ that links directly against the SQLite3 amalgamation. Queries run on the calling thread without crossing the libuv thread pool, eliminating async overhead. Prepared statements are compiled once and cached for repeated execution. The library manages database connections, statement finalization, and memory through C++ prevent leaks with RAII patterns.
Self-Hosting & Configuration
- Install via npm:
npm install better-sqlite3(prebuilt binaries for most platforms) - Open a database with
new Database('path.db')ornew Database(':memory:') - Enable WAL mode for concurrent reads:
db.pragma('journal_mode = WAL') - Configure busy timeout for lock contention:
db.pragma('busy_timeout = 5000') - Close the database explicitly with
db.close()when done
Key Features
- Synchronous API that is 2-5x faster than async SQLite bindings for typical workloads
- Transaction helper wraps multiple statements in a single atomic operation automatically
- User-defined functions written in JavaScript are callable from SQL queries
- Iterate method returns a generator for memory-efficient processing of large result sets
- Backup API copies databases to files or in-memory instances without blocking
Comparison with Similar Tools
- sql.js — SQLite compiled to WebAssembly; better-sqlite3 is faster with native bindings but does not run in browsers
- node-sqlite3 — Async API with callbacks; better-sqlite3 is faster and simpler with synchronous calls
- Drizzle ORM — TypeScript ORM that can use better-sqlite3 as its driver
- libSQL (Turso) — Fork of SQLite with replication; better-sqlite3 is for local single-node use
- PGlite — Embedded PostgreSQL in WebAssembly; better-sqlite3 offers a lighter footprint for simpler use cases
FAQ
Q: Why is a synchronous API faster for SQLite? A: SQLite is an embedded database that runs in-process. Wrapping it in async layers forces queries through the libuv thread pool, adding context-switch overhead. Synchronous calls execute directly on the main thread, which is faster for the typical sub-millisecond query times of SQLite.
Q: Does better-sqlite3 block the event loop? A: Yes, during query execution. For most SQLite queries (sub-millisecond), this is negligible. For long-running queries, use worker threads or consider a client-server database.
Q: Can multiple processes access the same database? A: Yes, with WAL mode enabled. Multiple processes can read concurrently, and writes are serialized by SQLite's locking mechanism. Use busy_timeout to handle lock contention.
Q: Does better-sqlite3 work with Electron? A: Yes. It is one of the most popular choices for Electron apps that need local storage. Use electron-rebuild to compile native bindings for the Electron runtime.