| title | SQLite Database Development Guide |
|---|---|
| description | Technical implementation details and best practices for SQLite integration in DeployStack Backend development. |
SQLite is the default database for DeployStack development and small to medium deployments. It provides excellent performance, zero configuration, and a simple file-based architecture that makes it ideal for development, testing, and single-server deployments.
Setup Instructions: For initial SQLite configuration, see the Database Setup Guide.
SQLite stores the entire database in a single file, making it extremely portable and easy to manage:
- Database File:
persistent_data/database/deploystack.db - Zero Configuration: No server setup or network configuration required
- ACID Compliance: Full transaction support with rollback capabilities
- Cross-Platform: Works identically across all operating systems
DeployStack uses the better-sqlite3 driver for optimal SQLite performance:
import { drizzle } from 'drizzle-orm/better-sqlite3';
import Database from 'better-sqlite3';
// Direct file-based connection
const sqlite = new Database(dbPath);
const db = drizzle(sqlite, { schema });Fast Local Operations:
- No network latency for database operations
- Direct file system access for maximum speed
- Excellent read performance for concurrent operations
Simple Deployment:
- Single file contains entire database
- No separate database server required
- Easy backup and restore operations
Development Friendly:
- Instant startup with no configuration
- Easy to reset and recreate for testing
- Perfect for local development workflows
Single Server Only:
- Cannot be shared across multiple application instances
- No built-in replication or clustering
- Limited to single-server deployments
Concurrent Write Limitations:
- Single writer at a time (multiple readers supported)
- Write operations are serialized
- May become a bottleneck under heavy write loads
SQLite is the recommended database for local development:
# SQLite requires no additional setup
DB_TYPE=sqlite
# Optional: Custom database path
SQLITE_DB_PATH=persistent_data/database/my-custom.dbDefault Location: services/backend/persistent_data/database/deploystack.db
Directory Structure:
services/backend/
├── persistent_data/
│ ├── database/
│ │ └── deploystack.db # Main database file
│ └── db.selection.json # Database type selection
SQLite is excellent for testing due to its simplicity:
// Test setup - create temporary database
const testDb = new Database(':memory:'); // In-memory for speed
// or
const testDb = new Database('test.db'); // File-based for persistence
// Run migrations
await migrate(drizzle(testDb), { migrationsFolder: './migrations' });
// Run tests
// ...
// Cleanup
testDb.close();SQLite excels at batch operations and can handle large global settings initialization efficiently:
- Large Batches: Can insert all 17+ global settings in a single transaction
- No Parameter Limits: Unlike D1, SQLite has no practical parameter limits
- Transaction Safety: All settings created atomically
// SQLite can handle large batch operations efficiently
await db.transaction(async (tx) => {
// Insert all settings in a single transaction
await tx.insert(globalSettings).values(allSettingsData);
await tx.insert(globalSettingGroups).values(allGroupsData);
});The SQLite command-line interface is the primary tool for database inspection:
# Open database
sqlite3 services/backend/persistent_data/database/deploystack.db
# Common commands
.tables # List all tables
.schema tablename # Show table schema
.headers on # Show column headers
.mode column # Format output in columns
# Query examples
SELECT * FROM globalSettings LIMIT 10;
SELECT COUNT(*) FROM users;
.quit # ExitDB Browser for SQLite (Recommended):
- Download: https://sqlitebrowser.org/
- Visual table browsing and editing
- Query execution with syntax highlighting
- Schema visualization
Other Options:
- SQLiteStudio: Cross-platform SQLite manager
- DBeaver: Universal database tool with SQLite support
- VS Code Extensions: SQLite Viewer, SQLite3 Editor
// Get database info
const info = db.prepare("PRAGMA database_list").all();
const tables = db.prepare("SELECT name FROM sqlite_master WHERE type='table'").all();
// Check table structure
const schema = db.prepare("PRAGMA table_info(globalSettings)").all();
// Performance analysis
const stats = db.prepare("PRAGMA compile_options").all();SQLite's file-based nature makes backup extremely simple:
# Simple file copy (when database is not in use)
cp persistent_data/database/deploystack.db backup/deploystack-$(date +%Y%m%d).db
# Using SQLite backup command (safe during operation)
sqlite3 persistent_data/database/deploystack.db ".backup backup/deploystack-$(date +%Y%m%d).db"#!/bin/bash
# backup-sqlite.sh
DB_PATH="persistent_data/database/deploystack.db"
BACKUP_DIR="backup"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p $BACKUP_DIR
# Create backup
sqlite3 $DB_PATH ".backup $BACKUP_DIR/deploystack-$DATE.db"
# Keep only last 7 days of backups
find $BACKUP_DIR -name "deploystack-*.db" -mtime +7 -delete
echo "Backup created: $BACKUP_DIR/deploystack-$DATE.db"# Restore from backup
cp backup/deploystack-20250103.db persistent_data/database/deploystack.db
# Or using SQLite restore
sqlite3 persistent_data/database/deploystack.db ".restore backup/deploystack-20250103.db"SQLite benefits greatly from proper indexing:
-- Example indexes for common queries
CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_global_settings_key ON globalSettings(key);
CREATE INDEX idx_sessions_user_id ON sessions(user_id);
CREATE INDEX idx_teams_created_at ON teams(created_at);Optimize SQLite performance with PRAGMA settings:
// Performance optimizations
db.pragma('journal_mode = WAL'); // Write-Ahead Logging
db.pragma('synchronous = NORMAL'); // Balanced safety/performance
db.pragma('cache_size = 1000000'); // 1GB cache
db.pragma('temp_store = MEMORY'); // Use memory for temp tablesWhile SQLite doesn't need traditional connection pooling, you can optimize connection usage:
// Reuse single connection
const sqlite = new Database(dbPath, {
readonly: false,
fileMustExist: false,
timeout: 5000,
verbose: process.env.NODE_ENV === 'development' ? (message) => {
server.log.debug({ operation: 'sqlite_query' }, message);
} : undefined
});
// Enable WAL mode for better concurrency
sqlite.pragma('journal_mode = WAL');SQLite has some unique characteristics for migrations:
-- SQLite doesn't support all ALTER TABLE operations
-- Instead of ALTER COLUMN, you need to recreate the table
-- Example: Adding a column (supported)
ALTER TABLE users ADD COLUMN phone TEXT;
-- Example: Changing column type (not supported directly)
-- Requires table recreation:
CREATE TABLE users_new (
id TEXT PRIMARY KEY,
email TEXT NOT NULL,
name TEXT NOT NULL,
age INTEGER -- Changed from TEXT to INTEGER
);
INSERT INTO users_new SELECT id, email, name, CAST(age AS INTEGER) FROM users;
DROP TABLE users;
ALTER TABLE users_new RENAME TO users;- Test Migrations: Always test on a copy of production data
- Backup Before Migration: Create backup before applying migrations
- Use Transactions: Wrap migrations in transactions for rollback capability
- Check Constraints: Verify foreign key constraints after table recreation
"Database is locked"
- Cause: Another process has the database open
- Solution: Ensure only one application instance accesses the database
- Prevention: Use WAL mode for better concurrency
"No such table" errors
- Cause: Migrations haven't been applied
- Solution: Run
npm run db:upor restart the server - Check: Verify migration files exist in
drizzle/migrations_sqlite/
Poor performance
- Cause: Missing indexes or suboptimal queries
- Solution: Add appropriate indexes and optimize queries
- Analysis: Use
EXPLAIN QUERY PLANto analyze query performance
File corruption
- Cause: Unexpected shutdown or disk issues
- Solution: Restore from backup
- Prevention: Use WAL mode and regular backups
// Enable query logging
const db = drizzle(sqlite, {
schema,
logger: {
logQuery: (query, params) => {
server.log.debug({ operation: 'sqlite_query', query, params }, 'Executing query');
}
}
});
// Analyze query performance
const explain = db.prepare('EXPLAIN QUERY PLAN SELECT * FROM users WHERE email = ?').all('test@example.com');
server.log.debug({ operation: 'sqlite_explain', explain }, 'Query execution plan');Good For:
- Single-server applications
- Read-heavy workloads
- Small to medium datasets (< 1TB)
- Applications with predictable load patterns
- Embedded applications
Consider Alternatives When:
- Multiple application servers needed
- High concurrent write requirements
- Need for real-time replication
- Distributed deployment requirements
// Production SQLite configuration
const sqlite = new Database(dbPath, {
readonly: false,
fileMustExist: true,
timeout: 10000
});
// Production PRAGMA settings
sqlite.pragma('journal_mode = WAL');
sqlite.pragma('synchronous = NORMAL');
sqlite.pragma('cache_size = 2000000'); // 2GB cache
sqlite.pragma('mmap_size = 268435456'); // 256MB memory-mapped I/O
sqlite.pragma('optimize'); // Optimize database// Monitor database size and performance
const stats = {
fileSize: fs.statSync(dbPath).size,
pageCount: db.prepare('PRAGMA page_count').get(),
pageSize: db.prepare('PRAGMA page_size').get(),
walSize: fs.existsSync(dbPath + '-wal') ? fs.statSync(dbPath + '-wal').size : 0
};
server.log.info({ operation: 'sqlite_monitoring', stats }, 'Database statistics');SQLite provides optimal performance for global settings:
- Fast initialization: All settings created in single transaction
- No batching needed: No parameter limits to worry about
- Immediate consistency: All changes immediately visible
Plugins work seamlessly with SQLite:
- Table creation: Plugin tables created through standard migrations
- Data operations: Full SQL feature support
- Performance: Excellent performance for plugin data operations
SQLite migration advantages:
- Fast execution: Local file operations are very fast
- Transaction safety: Full rollback support for failed migrations
- Simple debugging: Easy to inspect database state during development
When you outgrow SQLite, DeployStack makes migration easy:
- Export Data: Use SQLite's
.dumpcommand - Transform Schema: Convert to target database format
- Update Configuration: Change database type in setup
- Import Data: Load data into new database
Consider hybrid approaches for scaling:
- Read Replicas: Use D1 or Turso for global read access
- Caching Layer: Add Redis for frequently accessed data
- Microservices: Split into multiple services with separate databases
For general database concepts and cross-database functionality, see the Database Development Guide.
For initial setup and configuration, see the Database Setup Guide.