Bun's Built-in SQLite Driver: When bun:sqlite Works and When It Hurts
Bun ships with a native SQLite driver. Here is how to use bun:sqlite without npm packages, where it blocks the event loop, and how to check if it fits your workload.
31 Mar 2026, 09:31 UTC

The dependency you don't have to install
Adding SQLite to a Node.js project usually means installing a native package such as better-sqlite3. That pulls in a build toolchain, platform-specific prebuilt binaries, or both. If you are deploying to a slim Linux container, the extra megabytes and the node-gyp rebuild step are real friction.
Bun takes a different route. The runtime includes a SQLite driver behind the bun:sqlite specifier. There is no node_modules entry, no compilation, and no architecture-specific download. The driver is part of the Bun binary itself.
What bun:sqlite actually gives you
The module exposes a Database class with synchronous and asynchronous methods. The synchronous side uses prepared statements in a style familiar to Web SQL users:
import { Database } from "bun:sqlite";
const db = new Database("app.db");
db.run("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)");
const insert = db.prepare("INSERT INTO users (name) VALUES (?)");
insert.run("Ada");
insert.run("Grace");
const rows = db.query("SELECT * FROM users").all();
console.log(rows);
db.close();
Save this as app.ts (or app.js) and run it with bun run app.ts in your terminal. You need write permission in the working directory because SQLite creates the app.db file on disk. If the script prints an array of rows and the file appears, the built-in driver is active.
Because the underlying engine is standard SQLite, features like full-text search, the JSON1 extension, and PRAGMA statements behave as documented by SQLite itself. You are not learning a Bun-specific dialect.
The event loop is the real constraint
SQLite operations inside bun:sqlite execute on the same thread as your JavaScript event loop. A quick SELECT is usually harmless. A long-running transaction or a bulk insert, however, blocks every pending HTTP request, timer, and promise until the database call returns.
This is the central engineering decision. For a CLI tool, a local script, or a read-mostly service with modest traffic, the synchronous model is a feature: it keeps your code linear and easy to reason about. For a high-concurrency API that performs frequent writes, it becomes a latency trap.
If you need to protect the event loop, move the database work into a Worker. Bun supports Web Workers, and a worker can own the database connection while the main thread handles requests. The trade-off is added complexity: you now manage message passing and lifecycle across threads.
Version assumptions and verification
The bun:sqlite API was introduced as experimental during the Bun v1.0 era. That means the method names, return shapes, and default behaviors can change between minor releases. Before you commit to it in production, verify two things.
First, confirm your runtime has the module. Run bun --version in your terminal to see the installed version, then check the Bun release notes or API documentation for that specific version. Second, run the import test above. If import { Database } from "bun:sqlite" resolves without error, the module is bundled in your binary.
If you are deploying to production, pin the Bun version in your Dockerfile or CI pipeline. An experimental API is a poor candidate for floating upgrades.
Trade-offs at a glance
| Factor | bun:sqlite | External npm driver |
|---|---|---|
| Install size | Zero extra packages | Adds native binary or build step |
| Startup | No dynamic linking delay | Loads native addon at require time |
| Event loop | Blocks on synchronous calls | Depends on driver design |
| Stability | Experimental in v1.0 era; verify per release | Mature, semver-tracked |
An actionable starting point
Use bun:sqlite when your application is small, your write volume is low, and you control the deployment environment. It is an excellent fit for local-first tools, embedded scripts, and prototypes where setup speed matters more than raw throughput.
Before you scale it, write a benchmark that mimics your real access pattern. Measure request latency under concurrent load. If p99 climbs because of database stalls, move the connection into a worker or split the write path into a separate process. If latency stays flat, you have saved yourself a dependency and a build step—at least until the next Bun release changes the API.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.