better-sqlite3 vs sqlite vs sqlite3
Choosing the Right SQLite Binding for Node.js Applications
better-sqlite3sqlitesqlite3Similar Packages:

Choosing the Right SQLite Binding for Node.js Applications

better-sqlite3, sqlite, and sqlite3 are all Node.js bindings for the SQLite database engine, allowing developers to store and query data locally without a separate database server. sqlite3 is the legacy, widely-used asynchronous binding based on callbacks and promises. better-sqlite3 is a modern, synchronous binding known for high performance and a simpler API, though it blocks the event loop during execution. sqlite (often associated with sql.js or pure JS implementations) typically refers to a WebAssembly-based or fully asynchronous wrapper designed for portability across environments like browsers and Node.js, prioritizing compatibility over raw speed.

Npm Package Weekly Downloads Trend

3 Years

Github Stars Ranking

Stat Detail

Package
Downloads
Stars
Size
Issues
Publish
License
better-sqlite307,41827.3 MB674 days agoMIT
sqlite093398.5 kB73 years agoMIT
sqlite306,4183.4 MB1745 months agoBSD-3-Clause

better-sqlite3 vs sqlite vs sqlite3: Architecture, Performance, and Use Cases

When adding a local database to a Node.js project, SQLite is the go-to choice for its zero-configuration setup and single-file storage. However, the JavaScript binding you choose dictates your application's architecture, performance ceiling, and deployment targets. The three main contenders—better-sqlite3, sqlite (often sql.js), and sqlite3—take fundamentally different approaches to bridging C++ and JavaScript. Let's break down how they work and when to use each.

⚡ Execution Model: Synchronous vs Asynchronous vs Portable

The most critical difference lies in how these libraries handle the Node.js event loop.

better-sqlite3 runs synchronously. Every query blocks the main thread until the database returns a result. While this sounds scary, it actually makes code easier to read and debug because you don't need await or callbacks for every step. It is incredibly fast for heavy workloads.

// better-sqlite3: Synchronous execution
const Database = require('better-sqlite3');
const db = new Database('my-db.sqlite');

// No await needed; runs immediately
const row = db.prepare('SELECT * FROM users WHERE id = ?').get(1);
console.log(row);

// Transactions are simple functions
const insert = db.prepare('INSERT INTO users (name) VALUES (?)');
const insertMany = db.transaction((names) => {
  for (const name of names) insert.run(name);
});
insertMany(['Alice', 'Bob']);

sqlite3 is fully asynchronous. It uses callbacks or promises to ensure the event loop never blocks. This is great for servers handling thousands of concurrent connections where a slow query shouldn't freeze other requests, but it leads to more complex code structures.

// sqlite3: Asynchronous execution with callbacks
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('my-db.sqlite');

db.get('SELECT * FROM users WHERE id = ?', [1], (err, row) => {
  if (err) throw err;
  console.log(row);
});

// Closing requires checking when all queries finish
db.close((err) => {
  if (err) console.error(err.message);
});

sqlite (typically referring to sql.js or similar WASM-based ports) compiles SQLite to WebAssembly. It can run synchronously or asynchronously depending on the wrapper, but its superpower is portability. It runs anywhere JavaScript runs, including browsers.

// sqlite (sql.js): Portable, often async initialization
const initSqlJs = require('sql.js');

(async () => {
  const SQL = await initSqlJs();
  const db = new SQL.Database();
  
  // Run queries in memory or load from file buffer
  db.run('CREATE TABLE users (id INT, name TEXT)');
  db.run('INSERT INTO users VALUES (?, ?)', [1, 'Alice']);
  
  const stmt = db.prepare('SELECT * FROM users WHERE id = :id');
  stmt.bind({ ':id': 1 });
  while (stmt.step()) {
    console.log(stmt.getAsObject());
  }
})();

📦 Installation and Binary Dependencies

How you install these packages affects your CI/CD pipeline and deployment strategy.

better-sqlite3 requires native compilation. It downloads pre-built binaries for most systems, but if your server OS isn't standard (like some ARM-based Lambdas or Alpine Linux), you might need Python and a C++ compiler installed to build it from source. This can slow down Docker builds if not cached correctly.

# May require build tools on some systems
npm install better-sqlite3

sqlite3 also relies on native binaries and has historically been notorious for installation headaches, especially when switching Node versions or OS architectures. It often forces a rebuild of the native module, which can fail in minimal container environments without extra setup.

# Frequently requires node-gyp rebuilds
npm install sqlite3

sqlite (sql.js) has zero native dependencies. It is pure JavaScript and WebAssembly. You can install it on any machine, cross-compile easily, and it works instantly in browser bundles without special webpack configuration for native modules.

# Pure JS/WASM, no build tools needed
npm install sql.js

🛠️ API Ergonomics and Error Handling

The developer experience varies significantly between synchronous and asynchronous styles.

better-sqlite3 shines with its prepared statement API. You prepare a statement once and reuse it efficiently. Errors are thrown as standard JavaScript exceptions, making them easy to catch with try...catch blocks.

// better-sqlite3: Clean error handling
try {
  const stmt = db.prepare('INSERT INTO logs (msg) VALUES (?)');
  stmt.run('System started');
} catch (err) {
  console.error('Database write failed:', err.message);
}

sqlite3 forces you into callback hell or promise chains unless you wrap it yourself. Error handling happens inside the callback, which can lead to swallowed errors if you forget to check the err argument.

// sqlite3: Error handling inside callbacks
db.run('INSERT INTO logs (msg) VALUES (?)', ['System started'], function(err) {
  if (err) {
    return console.error('Database write failed:', err.message);
  }
  console.log('Row inserted with ID:', this.lastID);
});

sqlite offers a C-like API adapted for JS. While powerful, it often requires manual binding of parameters and stepping through results, which feels more verbose than the high-level helpers in better-sqlite3.

// sqlite (sql.js): Manual stepping
const stmt = db.prepare('SELECT * FROM logs');
while (stmt.step()) {
  const row = stmt.getAsObject();
  // Process row
}
stmt.free(); // Must manually free memory

🌐 Environment Compatibility

Where your code runs is a hard constraint.

  • Node.js Desktop/Server: better-sqlite3 is the performance king. sqlite3 is the legacy alternative.
  • Web Browsers: Only sqlite (sql.js) works here. Native bindings cannot run in Chrome or Firefox.
  • Serverless/Edge: sqlite is safest. better-sqlite3 works on some platforms (like standard AWS Lambda) but fails on others that restrict synchronous I/O or native addons.

📊 Summary: Key Differences

Featurebetter-sqlite3sqlite3sqlite (sql.js)
ExecutionSynchronous (Blocking)Asynchronous (Non-blocking)Sync/Async (WASM)
Performance🚀 Fastest (Native)⚡ Moderate🐢 Slower (WASM overhead)
Browser Support❌ No❌ No✅ Yes
Setup ComplexityMedium (Native binaries)High (Binary issues common)Low (Pure JS)
API StyleModern, Prepared StatementsCallback/Promise basedC-style, Manual stepping
Best ForCLI tools, Electron, Backend APIsLegacy maintenanceBrowser apps, Portability

💡 The Big Picture

better-sqlite3 is the modern standard for Node.js development. If you are building a backend service, a desktop app with Electron, or a command-line tool, this is the package to pick. Its synchronous API removes the complexity of async database calls, and its speed is unmatched. Just ensure your deployment environment supports native modules.

sqlite (sql.js) is the universal soldier. Choose it if you need to share database logic between your Node backend and a React frontend, or if you are deploying to restrictive environments like Cloudflare Workers or browser-based IDEs. You trade some speed for the ability to run anywhere.

sqlite3 is the legacy option. While still widely installed, it offers no real advantage over better-sqlite3 for new projects. Its asynchronous nature adds code complexity without providing significant concurrency benefits for typical SQLite workloads, and its installation process is often more fragile. Reserve it for maintaining older applications that haven't been migrated yet.

Final Thought: For 90% of Node.js projects today, better-sqlite3 provides the best balance of speed, simplicity, and reliability. Reach for sqlite only when you strictly need browser compatibility or zero-native-dependency deployment.

How to Choose: better-sqlite3 vs sqlite vs sqlite3

  • better-sqlite3:

    Choose better-sqlite3 if you are building a high-performance Node.js backend, CLI tool, or desktop app (Electron) where blocking the event loop for milliseconds is acceptable. It is the best choice for complex queries, bulk operations, and transactional integrity due to its synchronous nature, which simplifies error handling and control flow. Avoid it if you need to run your code in a browser or a serverless environment that strictly forbids synchronous file I/O.

  • sqlite:

    Choose sqlite (or sql.js) if you need your database logic to run in multiple environments, including web browsers, React Native, or serverless edge functions where native binaries are not allowed. It is ideal for prototypes, small-scale data caching, or applications where portability is more critical than raw query speed. Be aware that performance will be lower than native bindings, and it may lack some advanced SQLite features depending on the specific implementation.

  • sqlite3:

    Choose sqlite3 only if you are maintaining a legacy codebase that already depends on it or if you require a non-blocking, asynchronous API for very specific I/O-bound workflows in older Node.js versions. For new projects, it is generally recommended to avoid sqlite3 in favor of better-sqlite3 due to the latter's superior performance, cleaner API, and active maintenance, as sqlite3 can be prone to installation issues with native binaries and has a more complex asynchronous control flow.

README for better-sqlite3

better-sqlite3 Build Status

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

select 1 row  get() select 100 rows   all()  select 100 rows iterate() 1-by-1insert 1 row run()insert 100 rows in a transaction
better-sqlite31x1x1x1x1x
sqlite and sqlite311.7x slower2.9x slower24.4x slower2.8x slower15.6x slower

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