Using Bun’s Built‑in SQLite Client for Zero‑Config Data Storage
Learn how Bun’s built‑in sqlite module lets you add a relational database to your app with no extra dependencies, plus a quick TODO API example and trade‑offs.
18 Sept 2026, 00:10 UTC

Problem: needing a lightweight relational store without extra setup
When you spin up a Bun service for a small API or a CLI tool, adding a separate database server can feel overkill. You still want the reliability of SQL, transactions, and querying, but you don’t want to manage another process, install native drivers, or worry about version mismatches.
Thesis: Bun’s built‑in bun:sqlite module gives you a zero‑configuration SQLite client that runs in‑process, letting you add SQL storage with just a few lines of code.
How it works
The bun:sqlite module is bundled with the Bun runtime starting from v1.0. It exposes an API that mirrors the familiar sqlite3 package but is fully asynchronous, so you can await queries or use callbacks.
Opening a database
import { Database } from 'bun:sqlite';
// In‑memory DB (lost when the process ends)
const memDb = new Database(':memory:');
// File‑based DB (persists to disk)
const fileDb = new Database('./data/app.sqlite');
No npm install is required; the module is resolved directly from the Bun binary.
Executing statements
All methods return promises when used with await. Parameter binding protects against SQL injection.
async function init() {
await fileDb.exec(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
completed INTEGER DEFAULT 0
);
`);
}
async function addTodo(title) {
const result = await fileDb.run(
'INSERT INTO todos (title) VALUES (?)',
title
);
return result.lastInsertRowid;
}
async function listTodos() {
const rows = await fileDb.all('SELECT * FROM todos ORDER BY id DESC');
return rows;
}
Worked example: a minimal TODO API
The following script creates an HTTP endpoint with Bun’s built‑in server, stores items in a SQLite file, and returns JSON. It is under 30 lines and can be run with bun run todo.js.
import { Database } from 'bun:sqlite';
import { serve } from 'bun';
const db = new Database('./todo.sqlite');
await db.exec(`
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT NOT NULL,
done INTEGER DEFAULT 0
);
`);
serve({
async fetch(req) {
const url = new URL(req.url);
if (req.method === 'GET' && url.pathname === '/tasks') {
const tasks = await db.all('SELECT * FROM tasks');
return new Response(JSON.stringify(tasks), {
headers: { 'Content-Type': 'application/json' }
});
}
if (req.method === 'POST' && url.pathname === '/tasks') {
const { text } = await req.json();
const info = await db.run('INSERT INTO tasks (text) VALUES (?)', text);
const task = { id: info.lastInsertRowid, text, done: 0 };
return new Response(JSON.stringify(task), {
status: 201,
headers: { 'Content-Type': 'application/json' }
});
}
return new Response('Not found', { status: 404 });
},
port: 3000
});
console.log('🚀 TODO API listening on http://localhost:3000');
Run the file, then curl http://localhost:3000/tasks to see an empty array, and curl -X POST -H 'Content-Type: application/json' -d '{"text":"Learn bun:sqlite"}' http://localhost:3000/tasks to add a record.
Trade‑offs and limitations
- Single‑writer concurrency: SQLite allows multiple readers but only one writer at a time. In a high‑traffic Bun service, heavy write bursts can block the event loop because the database runs in the same thread.
- Missing extensions: The bundled SQLite omits some optional features like user‑defined functions and loadable extensions. If you need custom SQL functions, you must bring in an external SQLite build.
- Bundle size impact: Although the module is built‑in, enabling it adds the SQLite engine to the Bun binary. The increase is modest (~1‑2 MB) but worth noting for extremely size‑constrained environments.
Practical verification steps
- Check your Bun version:
bun --version. Thebun:sqliteAPI is stable from v1.0; older releases may need the--experimentalflag. - Run a quick test script:
import { Database } from 'bun:sqlite';
const db = new Database(':memory:');
const version = await db.get('SELECT sqlite_version() AS v');
console.log('SQLite version:', version.v);
If the script prints a version string (e.g., 3.45.0) without error, the feature is functional.
When to reach for a dedicated database
If your application expects sustained write throughput (> ~50 writes / second) or requires advanced SQL features beyond core SQLite, consider offloading to a separate process such as PostgreSQL or MySQL, or run SQLite in a dedicated worker thread via Worker to keep the main event loop responsive.
Closing
Bun’s bun:sqlite removes the friction of adding a relational store to a Bun project. For prototypes, internal tools, or low‑to‑moderate traffic APIs, it gives you SQL power with zero external dependencies. Just remember the write‑concurrency limit and verify the bundled SQLite version matches your expectations.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.