Fast SQLite database integration with prepared statements, transactions, type-safe operations, and high-performance query execution optimized for modern web applications.
import { Database } from "bun:sqlite";Create and manage SQLite database connections with comprehensive configuration options.
/**
* SQLite database connection with high-performance query execution
*/
class Database implements Disposable {
/**
* Create a new database connection
* @param filename - Database file path (use ":memory:" or undefined for in-memory database)
* @param options - Database configuration options
*/
constructor(filename?: string, options?: number | DatabaseOptions);
/**
* Create a new database connection (static factory method)
* @param filename - Database file path
* @param options - Database configuration options
*/
static open(filename: string, options?: number | DatabaseOptions): Database;
/** Database filename */
readonly filename: string;
/** Internal database handle */
readonly handle: number;
/** Whether database is currently in a transaction */
readonly inTransaction: boolean;
/**
* Close the database connection
* @param throwOnError - Whether to throw if database is in use
*/
close(throwOnError?: boolean): void;
/**
* Dispose method for using with disposable syntax
*/
[Symbol.dispose](): void;
}
interface DatabaseOptions {
/** Open database in read-only mode */
readonly?: boolean;
/** Allow creating new database file */
create?: boolean;
/** Open in read-write mode (default) */
readwrite?: boolean;
/** Return integers as bigint types instead of numbers */
safeIntegers?: boolean;
/** Enable strict parameter binding mode */
strict?: boolean;
}Usage Examples:
import { Database } from "bun:sqlite";
// Create/open database file
const db = new Database("myapp.db");
// In-memory database (faster, but data is lost when process exits)
const memoryDb = new Database(":memory:");
// Read-only database
const readOnlyDb = new Database("readonly.db", {
readonly: true,
mustExist: true
});
// Configuration options
const configuredDb = new Database("data.db", {
create: true, // Create if doesn't exist
readwrite: true // Read-write mode
});Prepare SQL statements for efficient repeated execution with parameter binding.
/**
* Prepare and cache a SQL statement for execution
* @param sql - SQL query string with optional parameter placeholders
* @returns Prepared statement ready for execution
*/
query<ReturnType, ParamsType extends SQLQueryBindings | SQLQueryBindings[]>(
sql: string
): Statement<ReturnType, ParamsType extends any[] ? ParamsType : [ParamsType]>;
/**
* Prepare a SQL statement (not cached)
* @param sql - SQL query string
* @param params - Optional parameter bindings
* @returns Prepared statement
*/
prepare<ReturnType, ParamsType extends SQLQueryBindings | SQLQueryBindings[]>(
sql: string,
params?: ParamsType
): Statement<ReturnType, ParamsType extends any[] ? ParamsType : [ParamsType]>;
/**
* Execute SQL without returning results (for DDL/DML statements)
* @param sql - SQL statement to execute
* @param bindings - Optional parameter bindings
* @returns Changes information
*/
run<ParamsType extends SQLQueryBindings[]>(sql: string, ...bindings: ParamsType[]): Changes;
/**
* Alias for run() (deprecated)
* @deprecated Use run() instead
*/
exec<ParamsType extends SQLQueryBindings[]>(sql: string, ...bindings: ParamsType[]): Changes;Usage Examples:
const db = new Database("users.db");
// Simple queries
const createTable = db.query(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
)
`);
// Parameterized queries (safer and faster)
const insertUser = db.query(`
INSERT INTO users (name, email) VALUES (?, ?)
`);
const getUserById = db.query(`
SELECT * FROM users WHERE id = ?
`);
const getUserByEmail = db.query(`
SELECT id, name, email FROM users WHERE email = $email
`);
// Complex queries with multiple parameters
const searchUsers = db.query(`
SELECT * FROM users
WHERE name LIKE ? AND created_at > ?
ORDER BY created_at DESC
LIMIT ?
`);Execute prepared statements with different return patterns for various use cases.
interface Statement<ReturnType = unknown, ParamsType extends SQLQueryBindings[] = any[]> implements Disposable {
/**
* Execute statement and return all matching rows
* @param params - Parameters to bind to the statement
* @returns Array of all matching rows
*/
all(...params: ParamsType): ReturnType[];
/**
* Execute statement and return first matching row
* @param params - Parameters to bind to the statement
* @returns First matching row or null if no results
*/
get(...params: ParamsType): ReturnType | null;
/**
* Execute statement and return execution info
* @param params - Parameters to bind to the statement
* @returns Execution result metadata
*/
run(...params: ParamsType): Changes;
/**
* Execute statement and return raw values as arrays
* @param params - Parameters to bind to the statement
* @returns Array of arrays containing raw column values
*/
values(...params: ParamsType): Array<Array<string | bigint | number | boolean | Uint8Array>>;
/**
* Execute statement and return raw bytes as Uint8Arrays
* @param params - Parameters to bind to the statement
* @returns Array of arrays containing raw byte data
*/
raw(...params: ParamsType): Array<Array<Uint8Array | null>>;
/**
* Execute statement and return an iterator over rows
* @param params - Parameters to bind to the statement
* @returns Iterator over result rows
*/
iterate(...params: ParamsType): IterableIterator<ReturnType>;
/**
* Iterator symbol implementation
*/
[Symbol.iterator](): IterableIterator<ReturnType>;
/**
* Column names in the result set
*/
readonly columnNames: string[];
/**
* Number of parameters in the prepared statement
*/
readonly paramsCount: number;
/**
* Actual column types from first row execution
*/
readonly columnTypes: Array<"INTEGER" | "FLOAT" | "TEXT" | "BLOB" | "NULL" | null>;
/**
* Declared column types from table schema
*/
readonly declaredTypes: Array<string | null>;
/**
* Finalize the statement and free resources
*/
finalize(): void;
/**
* Dispose method for using with disposable syntax
*/
[Symbol.dispose](): void;
/**
* Return expanded SQL string with bound parameters
*/
toString(): string;
/**
* Set custom class for result objects
* @param Class - Class constructor to use for results
*/
as<T = unknown>(Class: new (...args: any[]) => T): Statement<T, ParamsType>;
/**
* Native SQLite statement handle (untyped)
*/
readonly native: any;
}
interface Changes {
/** Number of rows affected by the statement */
changes: number;
/** Row ID of the last inserted row */
lastInsertRowid: number | bigint;
}Usage Examples:
const db = new Database("app.db");
// Setup table
db.exec(`
CREATE TABLE IF NOT EXISTS products (
id INTEGER PRIMARY KEY,
name TEXT NOT NULL,
price REAL NOT NULL,
category_id INTEGER
)
`);
const insertProduct = db.query(`
INSERT INTO products (name, price, category_id) VALUES (?, ?, ?)
`);
const getProduct = db.query(`
SELECT * FROM products WHERE id = ?
`);
const getAllProducts = db.query(`
SELECT * FROM products ORDER BY name
`);
const updatePrice = db.query(`
UPDATE products SET price = ? WHERE id = ?
`);
// Insert data
const result = insertProduct.run("Laptop", 999.99, 1);
console.log("Inserted product with ID:", result.lastInsertRowid);
console.log("Rows affected:", result.changes);
// Get single row
const product = getProduct.get(1);
if (product) {
console.log("Product:", product.name, product.price);
}
// Get all rows
const products = getAllProducts.all();
products.forEach(p => console.log(`${p.name}: $${p.price}`));
// Update data
const updateResult = updatePrice.run(899.99, 1);
console.log("Updated rows:", updateResult.changes);
// Raw values (arrays instead of objects)
const rawValues = db.query("SELECT name, price FROM products").values();
rawValues.forEach(([name, price]) => {
console.log(`${name}: $${price}`);
});Execute multiple operations atomically with automatic rollback on errors.
/**
* Create a transaction function that executes multiple operations atomically
* @param insideTransaction - Function to execute within the transaction
* @returns Transaction function with different transaction modes
*/
transaction<A extends any[], T>(
insideTransaction: (...args: A) => T
): {
/** Execute the transaction */
(...args: A): T;
/** Execute transaction using "BEGIN DEFERRED" */
deferred: (...args: A) => T;
/** Execute transaction using "BEGIN IMMEDIATE" */
immediate: (...args: A) => T;
/** Execute transaction using "BEGIN EXCLUSIVE" */
exclusive: (...args: A) => T;
};Usage Examples:
const db = new Database("banking.db");
db.exec(`
CREATE TABLE IF NOT EXISTS accounts (
id INTEGER PRIMARY KEY,
balance REAL NOT NULL
)
`);
const getBalance = db.query("SELECT balance FROM accounts WHERE id = ?");
const updateBalance = db.query("UPDATE accounts SET balance = ? WHERE id = ?");
// Atomic money transfer
const transferMoney = db.transaction((fromId: number, toId: number, amount: number) => {
const fromAccount = getBalance.get(fromId);
const toAccount = getBalance.get(toId);
if (!fromAccount || !toAccount) {
throw new Error("Account not found");
}
if (fromAccount.balance < amount) {
throw new Error("Insufficient funds");
}
// Both operations happen atomically
updateBalance.run(fromAccount.balance - amount, fromId);
updateBalance.run(toAccount.balance + amount, toId);
return { success: true, newBalance: fromAccount.balance - amount };
});
// Execute transfer (automatically rolled back on error)
try {
const result = transferMoney(1, 2, 100.00);
console.log("Transfer completed:", result);
} catch (error) {
console.error("Transfer failed:", error.message);
}
// Manual transaction control
const manualTx = db.begin();
try {
updateBalance.run(500, 1);
updateBalance.run(1500, 2);
manualTx.commit();
} catch (error) {
manualTx.rollback();
throw error;
}Direct SQL execution and database management operations.
/**
* Load a SQLite extension
* @param extension - Name/path of the extension to load
* @param entryPoint - Optional entry point of the extension
*/
loadExtension(extension: string, entryPoint?: string): void;
/**
* Change the dynamic library path to SQLite (macOS only)
* @param path - Path to the SQLite library
*/
static setCustomSQLite(path: string): boolean;
/**
* Serialize database to an in-memory Buffer
* @param name - Database name to serialize (default: "main")
* @returns Buffer containing serialized database
*/
serialize(name?: string): Buffer;
/**
* Load a serialized SQLite database
* @param serialized - Data to load
* @param isReadOnly - Whether database should be read-only
* @returns New Database instance
*/
static deserialize(serialized: NodeJS.TypedArray | ArrayBufferLike, isReadOnly?: boolean): Database;
/**
* Load a serialized SQLite database with options
* @param serialized - Data to load
* @param options - Database options
* @returns New Database instance
*/
static deserialize(
serialized: NodeJS.TypedArray | ArrayBufferLike,
options?: { readonly?: boolean; strict?: boolean; safeIntegers?: boolean }
): Database;
/**
* Call sqlite3_file_control for low-level database control
* @param op - Operation code
* @param arg - Optional argument
*/
fileControl(op: number, arg?: ArrayBufferView | number): number;
/**
* Call sqlite3_file_control with database name
* @param zDbName - Database name
* @param op - Operation code
* @param arg - Optional argument
*/
fileControl(zDbName: string, op: number, arg?: ArrayBufferView | number): number;Usage Examples:
const db = new Database("app.db");
// Execute DDL statements
db.exec(`
CREATE TABLE IF NOT EXISTS logs (
id INTEGER PRIMARY KEY,
message TEXT NOT NULL,
timestamp DATETIME DEFAULT CURRENT_TIMESTAMP
);
CREATE INDEX IF NOT EXISTS idx_logs_timestamp ON logs(timestamp);
`);
// Custom SQL functions
db.function("upper_first", (str: string) => {
return str.charAt(0).toUpperCase() + str.slice(1).toLowerCase();
});
// Use custom function in queries
const users = db.query("SELECT upper_first(name) as display_name FROM users").all();
// Serialize database for backup
const backup = db.serialize();
await Bun.write("backup.db", backup);
// Restore from backup
const restoredDb = Database.deserialize(backup);
// Clean up
db.close();Optimize database performance with pragma settings and connection options.
/**
* Execute PRAGMA statements for performance tuning
*/
interface DatabasePragmas {
/** Set journal mode (DELETE, TRUNCATE, PERSIST, MEMORY, WAL, OFF) */
journal_mode: string;
/** Set synchronous mode (OFF=0, NORMAL=1, FULL=2, EXTRA=3) */
synchronous: number;
/** Set cache size (negative = KB, positive = pages) */
cache_size: number;
/** Set temporary store (DEFAULT=0, FILE=1, MEMORY=2) */
temp_store: number;
/** Enable memory-mapped I/O */
mmap_size: number;
/** Optimize for specific workload */
optimize: boolean;
}Usage Examples:
const db = new Database("high_performance.db");
// Performance optimizations
db.exec("PRAGMA journal_mode = WAL"); // Write-Ahead Logging
db.exec("PRAGMA synchronous = NORMAL"); // Faster writes
db.exec("PRAGMA cache_size = -64000"); // 64MB cache
db.exec("PRAGMA temp_store = MEMORY"); // Temp tables in memory
db.exec("PRAGMA mmap_size = 268435456"); // 256MB memory map
// Batch operations for better performance
const insertMany = db.transaction((items: Array<{name: string, value: number}>) => {
const stmt = db.query("INSERT INTO items (name, value) VALUES (?, ?)");
for (const item of items) {
stmt.run(item.name, item.value);
}
});
// Insert 1000 items in a single transaction (much faster)
const items = Array.from({length: 1000}, (_, i) => ({
name: `item${i}`,
value: Math.random() * 100
}));
insertMany(items);/**
* SQLite constants from sqlite3.h
*/
namespace constants {
/** Open the database as read-only */
const SQLITE_OPEN_READONLY: number;
/** Open the database for reading and writing */
const SQLITE_OPEN_READWRITE: number;
/** Allow creating a new database */
const SQLITE_OPEN_CREATE: number;
/** Delete database on close */
const SQLITE_OPEN_DELETEONCLOSE: number;
/** Exclusive access */
const SQLITE_OPEN_EXCLUSIVE: number;
/** Auto proxy */
const SQLITE_OPEN_AUTOPROXY: number;
/** URI filename interpretation */
const SQLITE_OPEN_URI: number;
/** In-memory database */
const SQLITE_OPEN_MEMORY: number;
/** Main database file */
const SQLITE_OPEN_MAIN_DB: number;
/** Temporary database */
const SQLITE_OPEN_TEMP_DB: number;
/** Transient database */
const SQLITE_OPEN_TRANSIENT_DB: number;
/** Main journal file */
const SQLITE_OPEN_MAIN_JOURNAL: number;
/** Temporary journal file */
const SQLITE_OPEN_TEMP_JOURNAL: number;
/** Subjournal file */
const SQLITE_OPEN_SUBJOURNAL: number;
/** Super journal file */
const SQLITE_OPEN_SUPER_JOURNAL: number;
/** No mutex */
const SQLITE_OPEN_NOMUTEX: number;
/** Full mutex */
const SQLITE_OPEN_FULLMUTEX: number;
/** Shared cache */
const SQLITE_OPEN_SHAREDCACHE: number;
/** Private cache */
const SQLITE_OPEN_PRIVATECACHE: number;
/** WAL mode */
const SQLITE_OPEN_WAL: number;
/** No follow symlinks */
const SQLITE_OPEN_NOFOLLOW: number;
/** Extended result codes */
const SQLITE_OPEN_EXRESCODE: number;
// File control constants
const SQLITE_FCNTL_LOCKSTATE: number;
const SQLITE_FCNTL_GET_LOCKPROXYFILE: number;
const SQLITE_FCNTL_SET_LOCKPROXYFILE: number;
const SQLITE_FCNTL_LAST_ERRNO: number;
const SQLITE_FCNTL_SIZE_HINT: number;
const SQLITE_FCNTL_CHUNK_SIZE: number;
const SQLITE_FCNTL_FILE_POINTER: number;
const SQLITE_FCNTL_SYNC_OMITTED: number;
const SQLITE_FCNTL_WIN32_AV_RETRY: number;
const SQLITE_FCNTL_PERSIST_WAL: number;
const SQLITE_FCNTL_OVERWRITE: number;
const SQLITE_FCNTL_VFSNAME: number;
const SQLITE_FCNTL_POWERSAFE_OVERWRITE: number;
const SQLITE_FCNTL_PRAGMA: number;
const SQLITE_FCNTL_BUSYHANDLER: number;
const SQLITE_FCNTL_TEMPFILENAME: number;
const SQLITE_FCNTL_MMAP_SIZE: number;
const SQLITE_FCNTL_TRACE: number;
const SQLITE_FCNTL_HAS_MOVED: number;
const SQLITE_FCNTL_SYNC: number;
const SQLITE_FCNTL_COMMIT_PHASETWO: number;
const SQLITE_FCNTL_WIN32_SET_HANDLE: number;
const SQLITE_FCNTL_WAL_BLOCK: number;
const SQLITE_FCNTL_ZIPVFS: number;
const SQLITE_FCNTL_RBU: number;
const SQLITE_FCNTL_VFS_POINTER: number;
const SQLITE_FCNTL_JOURNAL_POINTER: number;
const SQLITE_FCNTL_WIN32_GET_HANDLE: number;
const SQLITE_FCNTL_PDB: number;
const SQLITE_FCNTL_BEGIN_ATOMIC_WRITE: number;
const SQLITE_FCNTL_COMMIT_ATOMIC_WRITE: number;
const SQLITE_FCNTL_ROLLBACK_ATOMIC_WRITE: number;
const SQLITE_FCNTL_LOCK_TIMEOUT: number;
const SQLITE_FCNTL_DATA_VERSION: number;
const SQLITE_FCNTL_SIZE_LIMIT: number;
const SQLITE_FCNTL_CKPT_DONE: number;
const SQLITE_FCNTL_RESERVE_BYTES: number;
const SQLITE_FCNTL_CKPT_START: number;
const SQLITE_FCNTL_EXTERNAL_READER: number;
const SQLITE_FCNTL_CKSM_FILE: number;
const SQLITE_FCNTL_RESET_CACHE: number;
}
/**
* SQLite query parameter bindings
*/
type SQLQueryBindings = string | bigint | NodeJS.TypedArray | number | boolean | null |
Record<string, string | bigint | NodeJS.TypedArray | number | boolean | null>;
/**
* SQLite error class
*/
class SQLiteError extends Error {
readonly name: "SQLiteError";
/** SQLite extended error code */
errno: number;
/** SQLite error code name */
code?: string;
/** UTF-8 byte offset of failed query */
readonly byteOffset: number;
}
/**
* Native SQLite module (lazily initialized)
*/
var native: any;
/**
* Default export is Database class
*/
export default Database;