better-sqlite3 is an MIT-licensed Node.js library that exposes SQLite3 through a synchronous, prepared-statement API, and it exists for Node.js developers who want a fast embedded SQL database running inside their own process instead of a client-server database or an asynchronous native binding.
What it is
better-sqlite3 is a JavaScript library published to the npm ecosystem that wraps SQLite3 for Node.js applications. Its README describes it as the fastest and simplest library for SQLite in Node.js, and it is tagged across the topics database, sql, sqlite, and sqlite3. It offers full transaction support, an easy-to-use synchronous API, high performance and safety, support for user-defined functions, aggregates, virtual tables and extensions, 64-bit integers, and worker thread support for large or slow queries. Databases are plain files: a single call such as const db = require('better-sqlite3')('foobar.db', options) opens one, and statements run through db.prepare('SELECT * FROM users WHERE id = ?').get(userId). ES module consumers can use import Database from 'better-sqlite3' instead.
The concrete problem it solves is the design of the binding it replaces. node-sqlite3 uses asynchronous APIs for tasks that are either CPU-bound or serialized, which wastes resources and causes mutex thrashing with damaging effects on performance, and it exposes low-level C memory management functions to the caller. better-sqlite3 handles memory the JavaScript way, letting the garbage collector do that work, and its synchronous API is argued in the README to give better concurrency than an asynchronous API for serialized SQLite workloads. It also ships utilities for operations that are difficult or impossible in node-sqlite3, and the README provides a benchmark script in docs/benchmark.md so the comparison can be reproduced.
Key capabilities
- Full transaction support, including batching many inserts inside one transaction.
- Synchronous prepared-statement API: get() for one row, all() for many rows, iterate() to walk rows one by one, and run() for inserts.
- User-defined functions, aggregates, virtual tables, and extensions.
- 64-bit integers, described as invisible until they are needed.
- Worker thread support for large or slow queries.
- Write-ahead logging through the pragma call db.pragma('journal_mode = WAL'), recommended for performance even though it is not required.
- Prebuilt binaries for major platforms and architectures, plus ES module import support.
Who uses it and how
- The README states the library is used by thousands of developers and engineers on a daily basis, and asks companies that depend on it to consider sponsoring through GitHub Sponsors, Patreon, or PayPal.
- Typical deployment is a Node.js process that opens a local .db file, prepares statements once, and executes them repeatedly, keeping the database in the same process as the application.
- The README reports measured results of upward of 2000 queries per second with 5-way joins in a 60 GB database, where each query handled 5 to 50 kilobytes of real data, given proper indexing.
- CPU-heavy or long-running queries can be pushed onto worker threads so the main thread stays responsive.
Getting started
Install it with npm install better-sqlite3, which requires a currently supported Node.js version and serves prebuilt binaries for major platforms and architectures. If installation fails, the project points readers to docs/troubleshooting.md.
How it compares
The README benchmarks better-sqlite3 at 1x against the sqlite and sqlite3 npm packages, which it measures as 11.7x slower on get() for a single row, 2.9x slower on all() for 100 rows, 24.4x slower on iterate() for 100 rows, 2.8x slower on run() for a single insert, and 15.6x slower on inserting 100 rows in a transaction. Against node-sqlite3 specifically, it argues that asynchronous APIs applied to CPU-bound or serialized work waste resources and cause mutex thrashing, and that exposing C-level memory management is the wrong shape for JavaScript. That places better-sqlite3 as the synchronous, garbage-collected alternative within the same SQLite-in-Node.js niche, rather than a different category of tool.
When to use it — and when not to
It is a poor fit for queries that take around a second to complete under many concurrent users, because no amount of asynchronicity overcomes the serialized nature of SQLite itself; the README advises looking at the query, the indexing, or the memory configuration first when performance problems appear. A self-hoster must accept a native module, meaning a supported Node.js version and platform-appropriate prebuilt binaries or a local build, and should set the WAL pragma for performance. The project carries an explicit funding ask and no compensation for its maintainer's work, and it had 73 open issues at the time of writing, so users who need commercial support or a guaranteed release cadence should weigh that.
project readme (upstream, from github) — read inline
better-sqlite3 
The fastest and simplest library for SQLite in Node.js.
- Full transaction support
- High performance, efficiency, and safety
- Easy-to-use synchronous API (better concurrency than an asynchronous API... yes, you read that correctly)
- Support for user-defined functions, aggregates, virtual tables, and extensions
- 64-bit integers (invisible until you need them)
- Worker thread support (for large/slow queries)
Help this project stay strong! 💪
better-sqlite3 is used by thousands of developers and engineers on a daily basis. Long nights and weekends were spent keeping this project strong and dependable, with no ask for compensation or funding, until now. If your company uses better-sqlite3, ask your manager to consider supporting the project:
How other libraries compare
You can verify these results by running the benchmark yourself.
Installation
npm install better-sqlite3
Requires a currently supported Node.js version. Prebuilt binaries are available for major platforms/architectures. If you have trouble installing, check the troubleshooting guide.
Usage
const db = require('better-sqlite3')('foobar.db', options);
const row = db.prepare('SELECT * FROM users WHERE id = ?').get(userId);
console.log(row.firstName, row.lastName, row.email);
Though not required, it is generally important to set the WAL pragma for performance reasons.
db.pragma('journal_mode = WAL');
In ES6 module notation:
import Database from 'better-sqlite3';
const db = new Database('foobar.db', options);
db.pragma('journal_mode = WAL');
Why should I use this instead of node-sqlite3?
node-sqlite3 uses asynchronous APIs for tasks that are either CPU-bound or serialized. That's not only bad design, but it wastes tons of resources. It also causes mutex thrashing which has devastating effects on performance.
node-sqlite3 exposes low-level (C language) memory management functions. better-sqlite3 does it the JavaScript way, allowing the garbage collector to worry about memory management.
better-sqlite3 is simpler to use, and it provides nice utilities for some operations that are very difficult or impossible in node-sqlite3.
better-sqlite3 is much faster than node-sqlite3 in most cases, and just as fast in all other cases.
When is this library not appropriate?
In most cases, if you're attempting something that cannot be reasonably accomplished with better-sqlite3, it probably cannot be reasonably accomplished with SQLite in general. For example, if you're executing queries that take one second to complete, and you expect to have many concurrent users executing those queries, no amount of asynchronicity will save you from SQLite's serialized nature. Fortunately, SQLite is very very fast. With proper indexing, we've been able to achieve upward of 2000 queries per second with 5-way-joins in a 60 GB database, where each query was handling 5–50 kilobytes of real data.
If you have a performance problem, the most likely causes are inefficient queries, improper indexing, or a lack of WAL mode—not better-sqlite3 itself. However, there are some cases where better-sqlite3 could be inappropriate:
- If you expect a high volume of concurrent reads each returning many megabytes of data (i.e., videos)
- If you expect a high volume of concurrent writes (i.e., a social media site)
- If your database's size is near the terabyte range
For these situations, you should probably use a full-fledged RDBMS such as PostgreSQL.
Upgrading
Upgrading your better-sqlite3 dependency can potentially introduce breaking changes, either in the better-sqlite3 API (if you upgrade to a new major version), or between your existing database(s) and the underlying version of SQLite. Before upgrading, review:
Documentation
License
MIT