Choosing the right SQLite driver for Node.js can impact your application’s performance.
We ran performance tests comparing better-sqlite3, node:sqlite, libSQL, and Turso across common database operations. Here’s what we found.
The Four SQLite Options for Node.js
This library has been tested in production for years, offering excellent performance through a synchronous API. The synchronous API makes it very performant and easy to use, and is perfect for fast queries.
Node.js 22+ includes a built-in SQLite module (still experimental). It provides zero-dependency SQLite access with a synchronous API similar to better-sqlite3, and also an asynchronous API.
→ Node.js SQlite Documentation
An open-source fork of SQLite created by the Turso team. It provides an async API and supports both local database files and remote libSQL/Turso servers. Fully compatible with the original SQLite (file format, API)
Turso is a SQLite compatible database written in Rust. Currently still in beta.
Benchmark Methodology
We tested all four drivers with identical queries. A simple scenario with a user and posts table.
- 10,000 users and 500,000 posts (50 per user)
- Optimized pragma settings (WAL mode, 64MB cache, memory-mapped I/O)
- Various query patterns: simple selects, indexed lookups, JOINs, aggregates, inserts, updates
Note: better-sqlite3 and node:sqlite use synchronous APIs, while libSQL and Turso use async/await.
SQLite Configuration
All databases used these optimized settings:
PRAGMA journal_mode = WAL; -- Write-Ahead Logging
PRAGMA synchronous = NORMAL; -- Balance safety/speed
PRAGMA cache_size = -64000; -- 64MB cache
PRAGMA temp_store = MEMORY; -- Temp tables in memory
PRAGMA mmap_size = 268435456; -- 256MB memory-mapped I/O
SQLite Driver Benchmark Report
Comparing better-sqlite3 vs node:sqlite vs libsql vs turso (baseline: node:sqlite)
System Information
| Node.js Version | v25.3.0 |
| Platform | linux |
| Architecture | x64 |
| CPU | 12th Gen Intel(R) Core(TM) i9-12900K |
| CPU Cores | 24 |
| Total Memory | 31.07 GB |
Summary
| Operation | better-sqlite3 | node:sqlite | libsql | turso | Winner | vs node:sqlite |
|---|---|---|---|---|---|---|
| getAllUsers | 360 ops/s | 268 ops/s | 50 ops/s | 104 ops/s | better-sqlite3 | 1.34x |
| getUserById | 1,223,260 ops/s | 1,073,001 ops/s | 61,093 ops/s | 707,859 ops/s | better-sqlite3 | 1.14x |
| getUserByEmail | 557,631 ops/s | 457,659 ops/s | 49,510 ops/s | 233,913 ops/s | better-sqlite3 | 1.22x |
| countUsers (pluck) | 538,031 ops/s | 398,431 ops/s | 108,632 ops/s | 5,593 ops/s | better-sqlite3 | 1.35x |
| getPostsByUser | 1,090,293 ops/s | 980,550 ops/s | 47,304 ops/s | 414,672 ops/s | better-sqlite3 | 1.11x |
| getPublishedPosts (JOIN) | 27 ops/s | 27 ops/s | 54 ops/s | 7 ops/s | libsql | 1.98x |
| getPostWithAuthor (JOIN :one) | 477,271 ops/s | 379,911 ops/s | 32,433 ops/s | 236,297 ops/s | better-sqlite3 | 1.26x |
| countPostsByUser (pluck) | 1,151,783 ops/s | 689,478 ops/s | 111,824 ops/s | 377,235 ops/s | better-sqlite3 | 1.67x |
| insertUser | 53,693 ops/s | 41,291 ops/s | 28,385 ops/s | 63,017 ops/s | turso | 1.53x |
| updatePostViews | 136,399 ops/s | 97,956 ops/s | 53,598 ops/s | 59,273 ops/s | better-sqlite3 | 1.39x |
- better-sqlite3 is the fastest for most operations, with node:sqlite second
- Turso has a surprisingly slow query for
countPostsByUser(better-sqlite3 is almost 100x faster here). I did not investigate why this is, it might be that the Turso database like many others (eg Postgresql) needs to scan the full table in order to count the number of rows and has no fast handling for this special case.
Benchmark Implementation
The benchmark is implemented using SQG, a SQL to code generator.
One advantage of using a code generator like SQG is that you can switch between SQLite drivers without rewriting your queries. SQG generates type-safe code for all four drivers from the same SQL file:
# sqg.yaml - generate code for multiple drivers
sql:
- files:
- queries.sql
gen:
- generator: typescript/sqlite/better-sqlite3
output: ./src/db-better-sqlite3.ts
- generator: typescript/sqlite/node
output: ./src/db-node-sqlite.ts
- generator: typescript/sqlite/libsql
output: ./src/db-libsql.ts
- generator: typescript/sqlite/turso
output: ./src/db-turso.ts
This makes it easy to benchmark with your actual queries and switch drivers by changing imports.
The benchmark code is available in our examples repository:
git clone https://github.com/sqg-dev/sqg
cd sqg/examples/typescript-sqlite-benchmark
pnpm install
pnpm generate
pnpm bench
This will generate an HTML report with the results table shown above.
I think for most applications, better-sqlite3 remains the best choice.
Benchmarks generated using SQG. Have questions? Open an issue on GitHub.