Library / SDK
WiseLibs/better-sqlite3 avatar
WiseLibs/better-sqlite3

better-sqlite3: A synchronous SQLite library for Node.js that outperforms async alternatives

The fastest and simplest library for SQLite3 in Node.js.

7,498 stars482 forksJavaScriptMIT

At a glance

What is it?
better-sqlite3 is a synchronous SQLite client that uses native bindings and does not serialize I/O. It is faster than the sqlite3 and sqlite packages at nearly every operation, and simpler to use.
Who is it for?
better-sqlite3 suits Node.js applications that need local SQLite storage and want simple, fast database operations without async complexity. It is most useful when your queries return quickly and your database is local to the application, such as Electron apps, CLI tools, or server-side applications with modest concurrency.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 51 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Prepared statements and the core API

The core of better-sqlite3 is the prepared statement. db.prepare(sql) wraps a SQL query and returns a statement object with three methods: get() returns a single row as an object, all() returns an array of all rows, and iterate() returns an iterable for memory-efficient row-by-row processing. Run queries with run(), which executes an INSERT, UPDATE, or DELETE and returns an object with changes and lastInsertRowid properties. Each prepared statement is reusable: call it again with different parameters and bind them with ? placeholders. The typical pattern is db.prepare('SELECT * FROM users WHERE id = ?').get(userId), binding userId at query time. Prepared statements prevent SQL injection and cache the compiled query, avoiding recompilation on each execution. Transactions wrap a function with db.transaction(fn), which rolls back if the function throws. Pragmas set SQLite options: db.pragma('journal_mode = WAL') enables write-ahead logging, and db.pragma('foreign_keys = on') enforces foreign key constraints.

Why synchronous SQLite wins over async libraries

better-sqlite3 uses a synchronous API by design. Synchronous calls offer better concurrency than an asynchronous API for SQLite, because SQLite itself serializes all writes to the database. The node-sqlite3 and sqlite packages use asynchronous APIs for tasks that are either CPU-bound or serialized by the database itself. This architectural choice wastes resources and causes mutex thrashing, which significantly damages performance. better-sqlite3 accepts that SQLite is inherently serialized and builds a simpler, faster library that lets the thread block when needed. This design is counterintuitive for Node.js developers accustomed to async/await, but it is correct: if SQLite is already serializing all writes, async operations provide no concurrency benefit. Instead, they add overhead and context switching costs. The methods available on the database object reflect this simplicity: db.prepare() wraps a SQL statement for reuse, db.exec() runs raw SQL, db.transaction() wraps a function for all-or-nothing execution, and db.pragma() sets SQLite options like journal_mode. Each call blocks until complete, but because SQLite is already serialized, this avoids the contention and scheduling overhead of async patterns.

Performance comparison

Comprehensive benchmark results show better-sqlite3's advantages in an inline comparison table. better-sqlite3 baseline is 1x across all operations. sqlite3 and sqlite are significantly slower: 11.7x slower on a single-row select using get(), 2.9x slower on selecting 100 rows with all(), 24.4x slower when iterating over 100 rows one-by-one, 2.8x slower on a single insert via run(), and 15.6x slower on 100 inserts in a transaction. The specific operations tested are get() for single row retrieval, all() for batch retrieval, iterate() for lazy row-by-row iteration, run() for inserts, and db.transaction() for atomic batches. The disparity in iteration speed (24.4x) highlights the overhead of async coordination in node-sqlite3. Verify these results by running the benchmark yourself: the benchmarking code lives in the benchmark/ directory of the repository.

Installation and basic usage

Install better-sqlite3 with:

bash
npm install better-sqlite3

Requires Node.js >=22. Prebuilt binaries are available for major platforms and architectures listed in the package.json exports: linux-x64, linux-arm64, linuxmusl-x64, linuxmusl-arm64, darwin-x64, darwin-arm64, win32-x64, and win32-arm64. If a prebuilt binary is unavailable for your platform, the build system downloads and compiles from source. Create a database and prepare a query:

js
const Database = require('better-sqlite3');
const db = new Database('foobar.db', options);
const row = db.prepare('SELECT * FROM users WHERE id = ?').get(userId);
console.log(row.firstName, row.lastName, row.email);

In ES6 module notation:

js
import Database from 'better-sqlite3';
const db = new Database('foobar.db', options);
db.pragma('journal_mode = WAL');

The Database constructor takes options as a second argument. Setting WAL pragma is recommended for performance. WAL (Write-Ahead Logging) changes SQLite from rollback journaling to write-ahead logging, reducing contention between reads and writes. If you have trouble installing, the repository includes a troubleshooting guide in docs/troubleshooting.md.

Transactions and advanced features

better-sqlite3 supports full transaction support, letting you wrap multiple statements in a transaction to ensure all-or-nothing semantics. If one statement fails, you can roll back all changes atomically. The library supports user-defined functions, aggregates, virtual tables, and extensions, letting you add custom SQL functions and capabilities beyond what standard SQLite provides. You can define custom SQL functions in JavaScript and call them within SQL queries. It handles 64-bit integers invisibly, so you work with JavaScript numbers that automatically widen to 64 bits when needed without any explicit casting. docs/integer.md covers this behavior in detail. Virtual tables allow you to present data from external sources as if they were SQL tables. Worker thread support offloads expensive queries to background threads without blocking the main thread. docs/threads.md covers the complete worker API, and docs/api.md documents the full method signatures. This is particularly useful when you have long-running analytical queries that would otherwise block your application.

Memory management and simplicity

node-sqlite3 exposes low-level C memory management functions to JavaScript, requiring you to manage resource cleanup. better-sqlite3 handles memory the JavaScript way, delegating to the garbage collector. This reduces complexity and the chance of memory leaks, and provides utilities for operations that are very difficult or impossible in node-sqlite3.

When SQLite itself is the wrong choice

If you are executing queries that take one second to complete and expect many concurrent users, SQLite's serialized nature means no amount of asynchronicity will help. If you need high-volume concurrent reads returning many megabytes of data, high-volume concurrent writes, or databases approaching terabyte scale, use a full-fledged RDBMS such as PostgreSQL. In other cases, improper indexing, inefficient queries, or lack of WAL mode are the most likely causes of poor performance, not better-sqlite3 itself.

Breaking changes and upgrade notes

Upgrading to a new major version of better-sqlite3 can introduce breaking changes in the API. The package.json records version 13.0.3, with recent releases including 13.0.2 and 13.0.1. SQLite itself can also introduce compatibility concerns between versions. Before upgrading, review the better-sqlite3 release notes and SQLite release history to understand what changed. The library tracks dependencies on node-addon-api for native binding support and includes dev dependencies for testing (mocha and chai) and benchmarking (nodemark). The build system uses node-gyp and binding.gyp for native compilation, and the src/ directory contains the C++ implementation.

better-sqlite3 vs node-sqlite3

better-sqlite3 differs from node-sqlite3 in three fundamental ways. First, node-sqlite3 exposes low-level C memory management, requiring you to free resources explicitly. better-sqlite3 uses JavaScript memory semantics, delegating to garbage collection. Second, node-sqlite3 uses asynchronous APIs for operations that SQLite itself serializes, creating overhead and mutex contention. better-sqlite3 is synchronous, avoiding this inefficiency. Third, better-sqlite3 provides utilities for common operations that are difficult or impossible in node-sqlite3. The performance gap is substantial: single-row selects are 11.7x faster, batch inserts are 15.6x faster, and iteration over results is 24.4x faster. Both libraries are MIT licensed, but better-sqlite3's simpler mental model and higher performance make it the better choice for most Node.js projects using local SQLite.

Editorial conclusion

better-sqlite3 suits Node.js applications that need local SQLite storage and want simple, fast database operations without async complexity. It is most useful when your queries return quickly and your database is local to the application, such as Electron apps, CLI tools, or server-side applications with modest concurrency. It is not suitable for high-volume concurrent write workloads (like a social media site), streaming large files from the database, or databases approaching terabyte scale. The README notes that with proper indexing, better-sqlite3 achieves over 2000 queries per second even on 60 GB databases with 5-way joins. Before upgrading to a new major version, check the release notes and SQLite release history, as either can introduce breaking changes. Start by installing better-sqlite3, test it with `npm test`, and run a basic query using db.prepare().get(); set db.pragma('journal_mode = WAL') for better performance once your schema is stable.

Frequently asked questions

Which is better, sqlite3 or better-sqlite3?

better-sqlite3 is faster in nearly every operation. The README shows it is 11.7x faster on single-row selects and 24.4x faster on iterating over results. It also has a simpler synchronous API.

Is better-sqlite3 safe?

Yes. better-sqlite3 uses prepared statements to prevent SQL injection and handles memory through the JavaScript garbage collector rather than exposing low-level C functions.

Is better-sqlite3 async?

No. better-sqlite3 is synchronous by design. The README explains that synchronous calls offer better concurrency than async APIs for SQLite because SQLite itself is serialized.

How do I install better-sqlite3?

Run npm install better-sqlite3. It requires Node.js >=22 and prebuilt binaries are available for major platforms.

What is the best alternative to SQLite?

For local application storage, better-sqlite3 is the best choice. For server-side databases with high concurrent writes or terabyte-scale data, use PostgreSQL or another full-featured RDBMS.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. WiseLibs/better-sqlite3 on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/wiselibs-better-sqlite3.svg)](https://hysenlabs.com/projects/wiselibs-better-sqlite3)