Storing data in a Bun program starts with choosing how it will work with SQLite. bun:sqlite is the driver included with Bun, providing a Database class and prepared statements for SQLite queries.
I’ll explain what bun:sqlite provides and walk through a small example that opens a database and uses a prepared statement.
TL;DR: Bun SQLite
Import Database from bun:sqlite, open a database, then prepare SQL with db.query(). Bind values when you execute the statement instead of building SQL by joining user input into a string.
- Use new Database(“:memory:”) for a temporary database or a filename for a local file.
- Use .get() for one row, .all() for an array, and .run() for a write.
- Wrap related writes in db.transaction() so they commit or roll back together.
What Is bun:sqlite?
bun:sqlite is the SQLite driver included with the Bun runtime. It exposes a Database class and prepared statements, so a Bun program can work with SQLite without installing a driver from npm.
The API is synchronous: a database call returns its result before JavaScript moves to the next line. That makes small scripts direct to write, but long queries also occupy the thread that called them. For related Node.js options, see our guide to SQLite in Node.js.
How to Use bun:sqlite in a Bun Project
This example creates a temporary database, defines a table, inserts two tasks in one transaction, and fetches one row. I ran the same SQL workflow with Bun 1.4.0.
Step 1: Open a database and create a table
Import Database and pass a filename to keep data on disk. This example uses :memory:, so the database disappears when the connection closes.
import { Database } from "bun:sqlite";
const db = new Database(":memory:");
db.run("CREATE TABLE tasks (id INTEGER PRIMARY KEY, title TEXT NOT NULL, done INTEGER NOT NULL DEFAULT 0)");
Step 2: Insert rows with a prepared statement
Prepare the insert once, then pass each title as a bound value. The placeholder keeps data separate from SQL syntax, and the same statement can run with different values.
const insert = db.query("INSERT INTO tasks (title) VALUES ($title)");
const addTasks = db.transaction((titles: string[]) => {
for (const title of titles) insert.run({ $title: title });
});
addTasks(["Read the Bun docs", "Build the example"]);
The named placeholder is $title, so the bound object uses the same key. Bun also supports positional placeholders such as ? when their order is clear.
Step 3: Read one row and close the connection
Call .get() to return the first matching row, then close the database when this work is done. The demo queries the row and total count.
const findTask = db.query("SELECT id, title, done FROM tasks WHERE title = $title");
console.log(findTask.get({ $title: "Build the example" }));
console.log(db.query("SELECT COUNT(*) AS count FROM tasks").get());
db.close();
{
id: 2,
title: "Build the example",
done: 0,
}
{
count: 2,
}
The first result is one task object, and the second confirms both inserts completed. Use .all() when the query can return multiple rows. It returns an array of row objects.
bun:sqlite Limits and Compatibility
Choose the database mode and result method based on the work: a file persists between runs, while an in-memory database suits tests that should start clean. The Bun docs also note that SQLite integer values default to JavaScript numbers, which cannot represent every 64-bit integer exactly.
| Need | Choice |
|---|---|
| Persist data locally | new Database(“tasks.sqlite”) |
| Temporary test data | new Database(“:memory:”) |
| One matching row | statement.get() |
| Several matching rows | statement.all() |
| Large integer values | Set the safeIntegers option to true to receive bigint values |
Transactions group writes into one unit. If the callback throws, Bun rolls the transaction back and propagates the error. I use a transaction for the two inserts above because the example treats them as one batch.
Do not assume a Node.js SQLite package can be swapped for bun:sqlite just because both expose a database object. Bun’s docs credit better-sqlite3 as an API influence, but native modules and libraries that expect a specific driver interface may still need their own adapter. A Bun discussion documents a better-sqlite3 Node ABI mismatch, so check the library’s Bun support before replacing its driver.
WAL is SQLite’s write-ahead logging mode, which lets readers continue while changes are written to a log. Bun documents enabling it with this command: PRAGMA journal_mode = WAL. Choose it for a workload that benefits from concurrent reads, not as a default performance promise.
Conclusion: Use Bun’s Built-In SQLite Driver
For a Bun program that needs local SQLite storage, bun:sqlite provides database connections, prepared statements and transactions without a separate driver install. Bind values, pick the result method that matches the query, and enable integer-safe handling when values can exceed JavaScript’s exact number range.
For a Node.js application instead, compare the approaches in our Node.js SQLite guide.
FAQ
These answers cover the first decisions when choosing Bun’s built-in SQLite API.
Does Bun include a SQLite driver?
Yes. Import the built-in Database class from bun:sqlite. No separate SQLite driver package is required for this API.
How do I get one row from bun:sqlite?
Prepare a query with db.query() and call its .get() method. It returns the first matching row or null when there is no row.
Are bun:sqlite calls asynchronous?
No. The bun:sqlite API is synchronous, so each database call returns before the next JavaScript statement runs.
