The SQLite adapter provides full support for SQLite databases using Bun's native SQLite driver. SQLite is perfect for development, testing, embedded applications, and small to medium-sized production applications.
- Native Bun SQLite: Uses Bun's built-in SQLite support (no external dependencies)
- File-based or In-Memory: Choose between persistent or ephemeral databases
- Full ACID Compliance: Complete transaction support
- Zero Configuration: No server setup required
- Schema Introspection: Full support for reading database metadata
- High Performance: Direct C binding through Bun
SQLite support is included with js-record - no additional dependencies needed when using Bun.
import { SqliteAdapter } from 'js-record';
// File-based database (persistent)
const adapter = new SqliteAdapter({
database: './myapp.db',
});
await adapter.connect();Perfect for testing:
const adapter = new SqliteAdapter({
database: ':memory:',
});
await adapter.connect();interface ConnectionConfig {
database: string; // File path or ':memory:'
}// Development database
const devAdapter = new SqliteAdapter({
database: './dev.db',
});
// Test database (in-memory)
const testAdapter = new SqliteAdapter({
database: ':memory:',
});
// Production database
const prodAdapter = new SqliteAdapter({
database: '/var/data/production.db',
});SQLite uses dynamic typing with the following storage classes:
| SQLite Type | JavaScript Type | Usage |
|---|---|---|
| INTEGER | number/bigint | Whole numbers, auto-increment IDs |
| REAL | number | Floating point numbers |
| TEXT | string | Strings, dates (ISO 8601) |
| BLOB | Buffer/Uint8Array | Binary data |
| NULL | null | Null values |
SQLite automatically converts between types when possible:
// Integer primary keys
id INTEGER PRIMARY KEY AUTOINCREMENT
// Text fields
name TEXT NOT NULL
email TEXT UNIQUE
// Numeric fields
price REAL
age INTEGER
// Dates (stored as TEXT)
created_at TEXT DEFAULT CURRENT_TIMESTAMP
// Boolean (stored as INTEGER 0/1)
is_active INTEGER DEFAULT 1
// JSON (stored as TEXT)
metadata TEXTawait adapter.execute(`
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE NOT NULL,
age INTEGER,
is_active INTEGER DEFAULT 1,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
)
`);// Create an index
await adapter.execute(`
CREATE INDEX idx_users_email ON users(email)
`);
// Unique index
await adapter.execute(`
CREATE UNIQUE INDEX idx_users_username ON users(username)
`);
// Composite index
await adapter.execute(`
CREATE INDEX idx_posts_user_created
ON posts(user_id, created_at)
`);SQLite supports foreign keys (enabled by default in this adapter):
await adapter.execute(`
CREATE TABLE posts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
title TEXT NOT NULL,
content TEXT,
FOREIGN KEY (user_id) REFERENCES users(id)
ON DELETE CASCADE
)
`);SQLite provides full ACID transactions:
const tx = await adapter.beginTransaction();
try {
await tx.execute('INSERT INTO users (name, email) VALUES (?, ?)', ['Alice', 'alice@example.com']);
const result = await tx.query('SELECT last_insert_rowid() as id');
const userId = result.rows[0].id;
await tx.execute('INSERT INTO profiles (user_id, bio) VALUES (?, ?)', [
userId,
'Software developer',
]);
await tx.commit();
} catch (error) {
await tx.rollback();
throw error;
}Always use parameterized queries to prevent SQL injection:
// ✅ Good - parameterized
const users = await adapter.query('SELECT * FROM users WHERE age > ? AND is_active = ?', [18, 1]);
// ❌ Bad - string concatenation (vulnerable to SQL injection)
const users = await adapter.query(`SELECT * FROM users WHERE age > ${age}`);// Slow - individual inserts
for (const user of users) {
await adapter.execute('INSERT INTO users (name, email) VALUES (?, ?)', [user.name, user.email]);
}
// Fast - batch with transaction
const tx = await adapter.beginTransaction();
try {
for (const user of users) {
await tx.execute('INSERT INTO users (name, email) VALUES (?, ?)', [user.name, user.email]);
}
await tx.commit();
} catch (error) {
await tx.rollback();
throw error;
}// Analyze slow queries
await adapter.query('EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = ?', ['test@example.com']);
// Add index for frequently queried columns
await adapter.execute('CREATE INDEX idx_users_email ON users(email)');// Enable write-ahead logging (better concurrency)
await adapter.execute('PRAGMA journal_mode = WAL');
// Increase cache size (in pages, default is 2000)
await adapter.execute('PRAGMA cache_size = 10000');
// Set synchronous mode (faster but less safe)
await adapter.execute('PRAGMA synchronous = NORMAL');const exists = await adapter.tableExists('users');const tables = await adapter.getTables();
console.log(tables); // ['users', 'posts', 'comments']const tableInfo = await adapter.getTableInfo('users');
console.log(tableInfo.columns);
console.log(tableInfo.indexes);await adapter.truncate('users');await adapter.dropTable('users');SQLite doesn't have a native date type. Store dates as TEXT in ISO 8601 format:
// Insert with current timestamp
await adapter.execute(
`
INSERT INTO events (name, occurred_at)
VALUES (?, datetime('now'))
`,
['User Login']
);
// Query by date range
const events = await adapter.query(
`
SELECT * FROM events
WHERE occurred_at BETWEEN ? AND ?
`,
['2025-01-01', '2025-12-31']
);
// Format dates in queries
await adapter.query(`
SELECT strftime('%Y-%m-%d', occurred_at) as date
FROM events
`);Store JSON data as TEXT:
// Create table with JSON column
await adapter.execute(`
CREATE TABLE settings (
id INTEGER PRIMARY KEY,
user_id INTEGER,
preferences TEXT -- JSON data
)
`);
// Insert JSON data
const prefs = { theme: 'dark', language: 'en' };
await adapter.execute('INSERT INTO settings (user_id, preferences) VALUES (?, ?)', [
1,
JSON.stringify(prefs),
]);
// Query JSON data
const result = await adapter.query('SELECT * FROM settings WHERE user_id = ?', [1]);
const preferences = JSON.parse(result.rows[0].preferences);SQLite locks the entire database during writes:
- Readers: Multiple simultaneous readers are fine
- Writers: Only one writer at a time
- Solution: Use WAL mode for better concurrency
await adapter.execute('PRAGMA journal_mode = WAL');SQLite is file-based and doesn't support remote connections:
- ✅ Single-server applications
- ✅ Embedded applications
- ❌ Distributed systems
- ❌ High-traffic web applications with multiple servers
SQLite uses type affinity, not strict types:
// This works but may cause issues
await adapter.execute(`
INSERT INTO users (age) VALUES ('not a number')
`);Solution: Use constraints and validation in your application.
SQLite has no built-in user/permission system. File system permissions control access.
try {
await adapter.connect();
// ... operations ...
} finally {
await adapter.disconnect();
}// Enable at connection (done automatically in this adapter)
await adapter.execute('PRAGMA foreign_keys = ON');import { copyFileSync } from 'fs';
// Simple backup
copyFileSync('./myapp.db', './backup/myapp-backup.db');
// Or use SQLite backup API
await adapter.execute("VACUUM INTO '/path/to/backup.db'");const plan = await adapter.query('EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = ?', [
'test@example.com',
]);
console.log(plan.rows);SQLite is perfect for testing due to in-memory databases:
import { SqliteAdapter } from 'js-record';
describe('User Model', () => {
let adapter: SqliteAdapter;
beforeEach(async () => {
// Fresh database for each test
adapter = new SqliteAdapter({ database: ':memory:' });
await adapter.connect();
// Set up schema
await adapter.execute(`
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT UNIQUE
)
`);
});
afterEach(async () => {
await adapter.disconnect();
});
it('creates a user', async () => {
const result = await adapter.execute('INSERT INTO users (name, email) VALUES (?, ?)', [
'Alice',
'alice@example.com',
]);
expect(result.insertId).toBe(1);
});
});| PostgreSQL | SQLite |
|---|---|
| SERIAL, BIGSERIAL | INTEGER PRIMARY KEY AUTOINCREMENT |
| VARCHAR(n), TEXT | TEXT |
| INTEGER, BIGINT | INTEGER |
| DECIMAL, NUMERIC | REAL |
| BOOLEAN | INTEGER (0/1) |
| TIMESTAMP | TEXT (ISO 8601) |
| JSON, JSONB | TEXT |
| BYTEA | BLOB |
| ARRAY | TEXT (JSON serialized) |
// PostgreSQL
'SELECT * FROM users WHERE created_at > NOW() - INTERVAL \'1 day\'';
// SQLite
'SELECT * FROM users WHERE created_at > datetime(\'now\', \'-1 day\')';
// PostgreSQL
'RETURNING id';
// SQLite
'SELECT last_insert_rowid() as id';
// PostgreSQL
'$1, $2, $3';
// SQLite
'?, ?, ?';