🎯

sqlite

🎯Skill

from oriolrius/pki-manager-web

VibeIndex|
What it does

sqlite skill from oriolrius/pki-manager-web

sqlite

Installation

Install skill:
npx skills add https://github.com/oriolrius/pki-manager-web --skill sqlite
6
Last UpdatedDec 4, 2025

Skill Details

SKILL.md

Expert guidance for SQLite database with better-sqlite3 Node.js driver including database setup, queries, transactions, migrations, performance optimization, and integration with TypeScript. Use this when working with embedded databases, better-sqlite3 driver, or SQLite operations.

Overview

# SQLite with better-sqlite3

Expert assistance with SQLite database operations using the better-sqlite3 Node.js driver.

Overview

SQLite is a lightweight, embedded SQL database engine:

  • Zero Configuration: No server setup required, single file database
  • ACID Compliant: Full transaction support with rollback
  • High Performance: Excellent for read-heavy workloads
  • Portable: Single file, easy backup and distribution
  • better-sqlite3: Synchronous Node.js driver, faster than async alternatives

Installation

```bash

# Install better-sqlite3

npm install better-sqlite3

npm install --save-dev @types/better-sqlite3

# Optional: SQLite CLI tools

# Ubuntu/Debian

sudo apt-get install sqlite3

# macOS

brew install sqlite3

```

Basic Setup

Initialize Database

```typescript

import Database from 'better-sqlite3';

// Create or open database

const db = new Database('mydb.sqlite');

// In-memory database (for testing)

const memDb = new Database(':memory:');

// Read-only mode

const readDb = new Database('mydb.sqlite', { readonly: true });

// Enable WAL mode for better concurrency

db.pragma('journal_mode = WAL');

// Close database

db.close();

```

Database Configuration

```typescript

import Database from 'better-sqlite3';

const db = new Database('mydb.sqlite', {

verbose: console.log, // Log every SQL statement

fileMustExist: false, // Create if doesn't exist

});

// Recommended pragmas

db.pragma('journal_mode = WAL'); // Write-Ahead Logging

db.pragma('synchronous = NORMAL'); // Balance safety/performance

db.pragma('foreign_keys = ON'); // Enable foreign keys

db.pragma('temp_store = MEMORY'); // Use memory for temp tables

```

Creating Tables

Basic Table Creation

```typescript

// Create table

db.exec(`

CREATE TABLE IF NOT EXISTS users (

id TEXT PRIMARY KEY,

name TEXT NOT NULL,

email TEXT NOT NULL UNIQUE,

created_at INTEGER NOT NULL DEFAULT (unixepoch())

)

`);

// Create index

db.exec(`

CREATE INDEX IF NOT EXISTS idx_users_email ON users(email)

`);

```

Complex Schema

```typescript

db.exec(`

CREATE TABLE IF NOT EXISTS certificate_authorities (

id TEXT PRIMARY KEY,

subject_dn TEXT NOT NULL,

serial_number TEXT NOT NULL UNIQUE,

not_before INTEGER NOT NULL,

not_after INTEGER NOT NULL,

kms_key_id TEXT NOT NULL,

certificate_pem TEXT NOT NULL,

is_root BOOLEAN NOT NULL DEFAULT 0,

parent_ca_id TEXT REFERENCES certificate_authorities(id),

status TEXT NOT NULL CHECK(status IN ('active', 'revoked', 'expired')),

created_at INTEGER NOT NULL DEFAULT (unixepoch()),

updated_at INTEGER NOT NULL DEFAULT (unixepoch())

);

CREATE TABLE IF NOT EXISTS certificates (

id TEXT PRIMARY KEY,

ca_id TEXT NOT NULL REFERENCES certificate_authorities(id) ON DELETE CASCADE,

subject_dn TEXT NOT NULL,

serial_number TEXT NOT NULL UNIQUE,

not_before INTEGER NOT NULL,

not_after INTEGER NOT NULL,

certificate_pem TEXT NOT NULL,

status TEXT NOT NULL CHECK(status IN ('active', 'revoked', 'expired')),

revocation_date INTEGER,

revocation_reason TEXT,

created_at INTEGER NOT NULL DEFAULT (unixepoch()),

updated_at INTEGER NOT NULL DEFAULT (unixepoch())

);

CREATE INDEX IF NOT EXISTS idx_certificates_ca_id ON certificates(ca_id);

CREATE INDEX IF NOT EXISTS idx_certificates_status ON certificates(status);

CREATE INDEX IF NOT EXISTS idx_certificates_serial ON certificates(serial_number);

`);

```

Queries

Prepared Statements

```typescript

// SELECT query

const getUser = db.prepare('SELECT * FROM users WHERE id = ?');

const user = getUser.get('user-123');

// SELECT all

const getAllUsers = db.prepare('SELECT * FROM users');

const users = getAllUsers.all();

// SELECT with multiple parameters

const findUsers = db.prepare('SELECT * FROM users WHERE name LIKE ? AND created_at > ?');

const results = findUsers.all('%John%', 1640000000);

// Named parameters

const getUserByEmail = db.prepare('SELECT * FROM users WHERE email = @email');

const user = getUserByEmail.get({ email: 'john@example.com' });

```

Insert Operations

```typescript

// Single insert

const insertUser = db.prepare(`

INSERT INTO users (id, name, email)

VALUES (?, ?, ?)

`);

const info = insertUser.run('user-123', 'John Doe', 'john@example.com');

console.log(Inserted ${info.changes} rows, last ID: ${info.lastInsertRowid});

// Insert with RETURNING (SQLite 3.35+)

const insertUserReturning = db.prepare(`

INSERT INTO users (id, name, email)

VALUES (?, ?, ?)

RETURNING *

`);

const newUser = insertUserReturning.get('user-456', 'Jane Doe', 'jane@example.com');

console.log('Created user:', newUser);

// Bulk insert (fast)

const insert = db.prepare('INSERT INTO users (id, name, email) VALUES (?, ?, ?)');

const insertMany = db.transaction((users) => {

for (const user of users) {

insert.run(user.id, user.name, user.email);

}

});

insertMany([

{ id: '1', name: 'Alice', email: 'alice@example.com' },

{ id: '2', name: 'Bob', email: 'bob@example.com' },

{ id: '3', name: 'Charlie', email: 'charlie@example.com' },

]);

```

Update Operations

```typescript

// Update

const updateUser = db.prepare(`

UPDATE users

SET name = ?, email = ?

WHERE id = ?

`);

const info = updateUser.run('John Smith', 'john.smith@example.com', 'user-123');

console.log(Updated ${info.changes} rows);

// Update with RETURNING

const updateReturning = db.prepare(`

UPDATE users

SET name = ?

WHERE id = ?

RETURNING *

`);

const updatedUser = updateReturning.get('New Name', 'user-123');

```

Delete Operations

```typescript

// Delete

const deleteUser = db.prepare('DELETE FROM users WHERE id = ?');

const info = deleteUser.run('user-123');

console.log(Deleted ${info.changes} rows);

// Delete with condition

const deleteOldUsers = db.prepare(`

DELETE FROM users

WHERE created_at < ?

`);

const info = deleteOldUsers.run(Date.now() - 86400000); // 24 hours ago

```

Transactions

Basic Transactions

```typescript

// Define transaction

const transferFunds = db.transaction((fromId, toId, amount) => {

const debit = db.prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?');

const credit = db.prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?');

debit.run(amount, fromId);

credit.run(amount, toId);

});

// Execute transaction (atomic)

transferFunds('account-1', 'account-2', 100);

```

Complex Transactions

```typescript

const createOrder = db.transaction((order, items) => {

// Insert order

const insertOrder = db.prepare(`

INSERT INTO orders (id, user_id, total)

VALUES (?, ?, ?)

RETURNING *

`);

const newOrder = insertOrder.get(order.id, order.userId, order.total);

// Insert order items

const insertItem = db.prepare(`

INSERT INTO order_items (order_id, product_id, quantity, price)

VALUES (?, ?, ?, ?)

`);

for (const item of items) {

insertItem.run(newOrder.id, item.productId, item.quantity, item.price);

}

// Update inventory

const updateInventory = db.prepare(`

UPDATE products

SET stock = stock - ?

WHERE id = ?

`);

for (const item of items) {

updateInventory.run(item.quantity, item.productId);

}

return newOrder;

});

// Use transaction

const order = createOrder(

{ id: 'order-1', userId: 'user-1', total: 150.00 },

[

{ productId: 'prod-1', quantity: 2, price: 50.00 },

{ productId: 'prod-2', quantity: 1, price: 50.00 },

]

);

```

Transaction Options

```typescript

// Immediate transaction (lock immediately)

const immediateTransaction = db.transaction((data) => {

// Operations

});

immediateTransaction.immediate(); // Optional: make it immediate

// Deferred transaction (default)

const deferredTransaction = db.transaction((data) => {

// Operations

});

deferredTransaction.deferred(); // Optional: make it deferred

// Exclusive transaction

const exclusiveTransaction = db.transaction((data) => {

// Operations

});

exclusiveTransaction.exclusive(); // Lock database exclusively

```

Advanced Queries

Joins

```typescript

const getUsersWithOrders = db.prepare(`

SELECT

u.id, u.name, u.email,

COUNT(o.id) as order_count,

SUM(o.total) as total_spent

FROM users u

LEFT JOIN orders o ON u.id = o.user_id

GROUP BY u.id

`);

const results = getUsersWithOrders.all();

```

Subqueries

```typescript

const getTopCustomers = db.prepare(`

SELECT *

FROM users

WHERE id IN (

SELECT user_id

FROM orders

GROUP BY user_id

HAVING SUM(total) > ?

)

`);

const topCustomers = getTopCustomers.all(1000);

```

Full-Text Search (FTS5)

```typescript

// Create FTS table

db.exec(`

CREATE VIRTUAL TABLE documents_fts USING fts5(

title,

content,

content=documents,

content_rowid=id

);

-- Populate FTS index

INSERT INTO documents_fts(rowid, title, content)

SELECT id, title, content FROM documents;

`);

// Search

const search = db.prepare(`

SELECT *

FROM documents d

JOIN documents_fts fts ON d.id = fts.rowid

WHERE documents_fts MATCH ?

ORDER BY rank

`);

const results = search.all('security AND encryption');

```

JSON Operations (SQLite 3.38+)

```typescript

// Store JSON

const insertWithJson = db.prepare(`

INSERT INTO users (id, name, metadata)

VALUES (?, ?, json(?))

`);

insertWithJson.run('user-1', 'John', JSON.stringify({ role: 'admin', age: 30 }));

// Query JSON

const getAdmins = db.prepare(`

SELECT *

FROM users

WHERE json_extract(metadata, '$.role') = 'admin'

`);

const admins = getAdmins.all();

```

TypeScript Integration

Type-Safe Queries

```typescript

import Database from 'better-sqlite3';

interface User {

id: string;

name: string;

email: string;

created_at: number;

}

const db = new Database('mydb.sqlite');

// Type-safe prepared statements

const getUserById = db.prepare<[string], User>('SELECT * FROM users WHERE id = ?');

const user: User | undefined = getUserById.get('user-123');

const getAllUsers = db.prepare<[], User>('SELECT * FROM users');

const users: User[] = getAllUsers.all();

// Insert with types

interface InsertUser {

id: string;

name: string;

email: string;

}

const insertUser = db.prepare<[string, string, string]>(`

INSERT INTO users (id, name, email)

VALUES (?, ?, ?)

`);

function createUser(user: InsertUser) {

return insertUser.run(user.id, user.name, user.email);

}

```

Database Class Wrapper

```typescript

import Database from 'better-sqlite3';

export class DatabaseClient {

private db: Database.Database;

constructor(filename: string) {

this.db = new Database(filename);

this.db.pragma('journal_mode = WAL');

this.db.pragma('foreign_keys = ON');

}

getUserById(id: string): User | undefined {

const stmt = this.db.prepare<[string], User>('SELECT * FROM users WHERE id = ?');

return stmt.get(id);

}

createUser(user: InsertUser): User {

const stmt = this.db.prepare<[string, string, string]>(`

INSERT INTO users (id, name, email)

VALUES (?, ?, ?)

RETURNING *

`);

return stmt.get(user.id, user.name, user.email)!;

}

close() {

this.db.close();

}

}

```

Migrations

Manual Migrations

```typescript

const migrations = [

// Migration 1

`CREATE TABLE users (

id TEXT PRIMARY KEY,

name TEXT NOT NULL,

email TEXT NOT NULL UNIQUE

)`,

// Migration 2

ALTER TABLE users ADD COLUMN created_at INTEGER DEFAULT (unixepoch()),

// Migration 3

CREATE INDEX idx_users_email ON users(email),

];

function migrate(db: Database.Database) {

// Create migrations table

db.exec(`

CREATE TABLE IF NOT EXISTS migrations (

id INTEGER PRIMARY KEY,

applied_at INTEGER NOT NULL DEFAULT (unixepoch())

)

`);

const getCurrentVersion = db.prepare('SELECT MAX(id) as version FROM migrations');

const currentVersion = (getCurrentVersion.get() as any).version || 0;

const insertMigration = db.prepare('INSERT INTO migrations (id) VALUES (?)');

// Run pending migrations

const runMigrations = db.transaction(() => {

for (let i = currentVersion; i < migrations.length; i++) {

console.log(Running migration ${i + 1});

db.exec(migrations[i]);

insertMigration.run(i + 1);

}

});

runMigrations();

}

// Run migrations

migrate(db);

```

Performance Optimization

Indexes

```typescript

// Create indexes for frequently queried columns

db.exec(`

CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);

CREATE INDEX IF NOT EXISTS idx_orders_user_id ON orders(user_id);

CREATE INDEX IF NOT EXISTS idx_orders_created_at ON orders(created_at);

-- Composite index

CREATE INDEX IF NOT EXISTS idx_orders_user_status

ON orders(user_id, status);

-- Partial index (SQLite 3.8+)

CREATE INDEX IF NOT EXISTS idx_active_users

ON users(email) WHERE status = 'active';

`);

// Analyze query performance

db.exec('ANALYZE');

```

Query Optimization

```typescript

// Use EXPLAIN QUERY PLAN

const plan = db.prepare('EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = ?');

console.log(plan.all('john@example.com'));

// Batch operations in transactions

const insertMany = db.transaction((users) => {

const insert = db.prepare('INSERT INTO users (id, name, email) VALUES (?, ?, ?)');

for (const user of users) {

insert.run(user.id, user.name, user.email);

}

});

// This is ~1000x faster than individual inserts

insertMany(largeUserArray);

```

Connection Settings

```typescript

// Optimize for performance

db.pragma('cache_size = -64000'); // 64MB cache

db.pragma('temp_store = MEMORY');

db.pragma('mmap_size = 30000000000'); // 30GB memory-mapped I/O

db.pragma('page_size = 4096'); // Match OS page size

// Check settings

console.log(db.pragma('cache_size', { simple: true }));

console.log(db.pragma('page_size', { simple: true }));

```

Backup and Restore

Backup Database

```typescript

import fs from 'fs';

// Simple file copy (database must be closed or in WAL mode)

function backupDatabase(source: string, dest: string) {

fs.copyFileSync(source, dest);

// Also copy WAL and SHM files if they exist

if (fs.existsSync(${source}-wal)) {

fs.copyFileSync(${source}-wal, ${dest}-wal);

}

if (fs.existsSync(${source}-shm)) {

fs.copyFileSync(${source}-shm, ${dest}-shm);

}

}

// Online backup using VACUUM INTO (SQLite 3.27+)

function vacuumBackup(db: Database.Database, dest: string) {

db.prepare(VACUUM INTO ?).run(dest);

}

// Export to SQL

function exportToSql(db: Database.Database, filename: string) {

const tables = db.prepare(`

SELECT name FROM sqlite_master

WHERE type='table' AND name NOT LIKE 'sqlite_%'

`).all() as { name: string }[];

let sql = '';

for (const { name } of tables) {

// Get CREATE statement

const createStmt = db.prepare(`

SELECT sql FROM sqlite_master WHERE name = ?

`).get(name) as { sql: string };

sql += createStmt.sql + ';\n\n';

// Get data

const rows = db.prepare(SELECT * FROM ${name}).all();

for (const row of rows) {

const values = Object.values(row).map(v =>

typeof v === 'string' ? '${v.replace(/'/g, "''")}' : v

).join(', ');

sql += INSERT INTO ${name} VALUES (${values});\n;

}

sql += '\n';

}

fs.writeFileSync(filename, sql);

}

```

Error Handling

```typescript

import Database from 'better-sqlite3';

try {

const result = db.prepare('INSERT INTO users (id, email) VALUES (?, ?)').run('1', 'test@example.com');

} catch (error) {

if (error instanceof Database.SqliteError) {

switch (error.code) {

case 'SQLITE_CONSTRAINT_UNIQUE':

console.error('Unique constraint violation');

break;

case 'SQLITE_CONSTRAINT_FOREIGNKEY':

console.error('Foreign key constraint violation');

break;

default:

console.error('Database error:', error.message);

}

}

}

```

Testing

```typescript

import Database from 'better-sqlite3';

// Use in-memory database for tests

let testDb: Database.Database;

beforeEach(() => {

testDb = new Database(':memory:');

testDb.pragma('foreign_keys = ON');

// Setup schema

testDb.exec(`

CREATE TABLE users (

id TEXT PRIMARY KEY,

name TEXT NOT NULL,

email TEXT NOT NULL UNIQUE

)

`);

});

afterEach(() => {

testDb.close();

});

test('creates user', () => {

const insert = testDb.prepare('INSERT INTO users VALUES (?, ?, ?)');

const info = insert.run('1', 'John', 'john@example.com');

expect(info.changes).toBe(1);

const user = testDb.prepare('SELECT * FROM users WHERE id = ?').get('1');

expect(user).toEqual({ id: '1', name: 'John', email: 'john@example.com' });

});

```

Best Practices

  1. Use WAL Mode: Better concurrency with journal_mode = WAL
  2. Enable Foreign Keys: Always set foreign_keys = ON
  3. Use Transactions: Batch operations in transactions for performance
  4. Prepared Statements: Reuse prepared statements for frequently executed queries
  5. Index Strategically: Index columns used in WHERE, JOIN, ORDER BY
  6. Regular VACUUM: Run VACUUM periodically to defragment
  7. Backup Regularly: Implement automated backup strategy
  8. Monitor Size: SQLite works best under 1TB
  9. Connection Pooling: Use single connection per process (better-sqlite3 is synchronous)
  10. Error Handling: Handle constraint violations gracefully

Common Patterns

Repository Pattern

```typescript

export class UserRepository {

private db: Database.Database;

private getByIdStmt: Database.Statement<[string]>;

private getAllStmt: Database.Statement<[]>;

private insertStmt: Database.Statement<[string, string, string]>;

private updateStmt: Database.Statement<[string, string, string]>;

private deleteStmt: Database.Statement<[string]>;

constructor(db: Database.Database) {

this.db = db;

// Prepare statements once

this.getByIdStmt = db.prepare('SELECT * FROM users WHERE id = ?');

this.getAllStmt = db.prepare('SELECT * FROM users');

this.insertStmt = db.prepare('INSERT INTO users (id, name, email) VALUES (?, ?, ?) RETURNING *');

this.updateStmt = db.prepare('UPDATE users SET name = ?, email = ? WHERE id = ? RETURNING *');

this.deleteStmt = db.prepare('DELETE FROM users WHERE id = ?');

}

findById(id: string): User | undefined {

return this.getByIdStmt.get(id) as User | undefined;

}

findAll(): User[] {

return this.getAllStmt.all() as User[];

}

create(user: Omit): User {

return this.insertStmt.get(user.id, user.name, user.email) as User;

}

update(id: string, data: Partial): User | undefined {

return this.updateStmt.get(data.name, data.email, id) as User | undefined;

}

delete(id: string): boolean {

const info = this.deleteStmt.run(id);

return info.changes > 0;

}

}

```

Resources

  • SQLite Documentation: https://www.sqlite.org/docs.html
  • better-sqlite3 Documentation: https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md
  • SQLite Tutorial: https://www.sqlitetutorial.net/
  • Performance Tips: https://www.sqlite.org/performance.html