SQLite CRUD
dbQuery(path, sql, params) runs against an embedded
SQLite file in the bext bridge. The schema is created lazily on first request; the
loader reads, the action writes, and a 303 redirect re-runs the
loader so the list reflects the mutation.
Tip
dbQuery returns rows as positional arrays (not objects) — zip them with r.columns. The .db file is created automatically under .bext/data/; no migrations or configuration needed.
Add a note
Notes (0)
no notes yet — add one above.
// src/app/examples/db-crud/page.tsx
// SQLite via the bext bridge — bundled SQLite, no extra deps.
import { dbQuery } from "@bext-stack/framework/bridge";
const DB = ".bext/data/notes.db";
dbQuery(DB, `CREATE TABLE IF NOT EXISTS notes (
id INTEGER PRIMARY KEY AUTOINCREMENT,
body TEXT NOT NULL,
created_at INTEGER NOT NULL
)`);
export async function loader() {
const r = dbQuery(DB, "SELECT id, body, created_at FROM notes ORDER BY id DESC LIMIT 20");
// rows come back as positional arrays — zip with r.columns:
const notes = r.rows.map((row) =>
Object.fromEntries(r.columns.map((c, i) => [c, row[i]]))
);
return { notes };
}
export async function action({ request }) {
const form = await request.formData();
if (form.get("op") === "add") {
dbQuery(DB, "INSERT INTO notes (body, created_at) VALUES (?, ?)",
[form.get("body"), Date.now()]);
} else if (form.get("op") === "delete") {
dbQuery(DB, "DELETE FROM notes WHERE id = ?", [form.get("id")]);
}
return new Response(null, { status: 303, headers: { Location: "/examples/db-crud" } });
}