SQLite
Bun ships with a built-in SQLite driver. Zero dependencies, zero setup. Create a database, share it via context, query it in your routes.
Install
Nothing to install. bun:sqlite is built into Bun and available in every project. Just import it.
Setup in server.ts
Create the database once at startup and share it with every request via a typed context key.
// server.ts
import { createRouter } from '@cmj/juice/runtime';
import { createContextKey, setContext } from '@cmj/juice/runtime';
import { Database } from 'bun:sqlite';
// Create or open a database file
const db = new Database('app.db');
// Enable WAL mode for better concurrent read performance
db.run('PRAGMA journal_mode = WAL');
// Typed context key
export const dbKey = createContextKey<Database>('db');
const router = createRouter({
onBeforeRequest: async (req) => {
setContext(req, dbKey, db);
},
// ... routes
});
export default {
fetch: router.fetch,
};Schema Creation
Run your schema setup at startup, right after creating the database. Use IF NOT EXISTS so it is safe to run on every start.
// server.ts (after creating db)
db.run(`
CREATE TABLE IF NOT EXISTS tasks (
id INTEGER PRIMARY KEY AUTOINCREMENT,
title TEXT NOT NULL,
done INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT (datetime('now'))
)
`);Query in Server Components
Server components receive the request prop. Use it to pull the database from context and run queries directly.
// app/routes/home.tsx
import React from 'react';
import { getContext } from '@cmj/juice/runtime';
import { dbKey } from '../../server.js';
interface Task {
id: number;
title: string;
done: number;
created_at: string;
}
export default function Home({ request }: { request: Request }) {
const db = getContext(request, dbKey);
const tasks = db.query('SELECT * FROM tasks ORDER BY created_at DESC').all() as Task[];
return (
<div>
<h2>Tasks</h2>
<ul>
{tasks.map(task => (
<li key={task.id}>
{task.done ? '\u2713' : '\u25CB'} {task.title}
</li>
))}
</ul>
</div>
);
}
export const response = {
head: { title: 'Tasks' },
};Insert via Server Actions
Use a server action to handle form submissions. The action receives the FormData and can access the database through context on the current request.
// app/routes/add.tsx
import React from 'react';
import { getContext, redirect } from '@cmj/juice/runtime';
import { dbKey } from '../../server.js';
async function addTask(formData: FormData) {
'use server';
const title = formData.get('title') as string;
if (!title?.trim()) {
return { error: 'Title is required' };
}
// Access db from the action's request context
const db = getContext(this.request, dbKey);
db.run('INSERT INTO tasks (title) VALUES (?)', [title.trim()]);
redirect('/', 303);
}
export default function AddTask() {
return (
<div>
<h2>Add Task</h2>
<form action={addTask} method="POST">
<input name="title" placeholder="Task title" required />
<button type="submit">Add</button>
</form>
</div>
);
}
export const response = {
head: { title: 'Add Task' },
};Deploying to Cloudflare? Use D1 instead of bun:sqlite. The pattern is identical — create the D1 binding, share it via context, query it in your routes. Only the driver changes. See the Cloudflare D1 docs for the driver API.
Other Runtimes
The examples above use bun:sqlite, which is built into Bun. No changes needed — this is the default path.
Use Cloudflare D1 instead of bun:sqlite. D1 is accessed through an environment binding and uses a prepare/bind/all query API.
// wrangler.toml
// [[d1_databases]]
// binding = "DB"
// database_name = "my-db"
// database_id = "..."
// server.ts — access D1 via env binding
export default {
async fetch(req: Request, env: { DB: D1Database }) {
setContext(req, dbKey, env.DB);
return handler(req);
},
};
// In routes — D1 uses prepare/bind/all
const tasks = await db.prepare('SELECT * FROM tasks').all();
await db.prepare('INSERT INTO tasks (id, title) VALUES (?, ?)').bind(id, title).run();// Use npm:better-sqlite3 or Deno KV
import Database from 'npm:better-sqlite3';
const db = new Database('app.db');
// Same API as bun:sqlite// Install: npm install better-sqlite3
import Database from 'better-sqlite3';
const db = new Database('app.db');
// Same API as bun:sqlite