Database Integration Layer
The Coherent.js database integration layer provides a comprehensive ORM and query builder system with support for multiple database engines, migrations, and seamless router integration.
Features
- Multi-Database Support: PostgreSQL, MySQL, SQLite, MongoDB
- Connection Pooling: Automatic connection management with configurable pools
- ORM Models: ActiveRecord-style models with relationships and validation
- Query Builder: Fluent interface for building complex SQL queries
- Migration System: Version-controlled schema management
- Transaction Support: ACID transactions with automatic rollback
- Router Middleware: Seamless integration with Coherent.js router
- Type Safety: Built-in type casting and validation
Quick Start
Installation
# Core database layer (no dependencies)
npm install @coherent/database
# Add database drivers as needed
npm install sqlite3 # For SQLite
npm install pg # For PostgreSQL
npm install mysql2 # For MySQL
npm install mongodb # For MongoDB
Basic Setup
import { DatabaseManager, createModel } from '@coherent/database';
// Configure database
const db = new DatabaseManager({
type: 'postgresql',
host: 'localhost',
database: 'myapp',
username: 'user',
password: 'pass',
pool: { min: 2, max: 10 }
});
// Connect
await db.connect();
Database Configuration
SQLite Configuration
const config = {
type: 'sqlite',
database: './app.db',
pool: { min: 2, max: 10 }
};
PostgreSQL Configuration
const config = {
type: 'postgresql',
host: 'localhost',
port: 5432,
database: 'myapp',
username: 'postgres',
password: 'password',
ssl: false,
pool: {
min: 2,
max: 10,
acquireTimeoutMillis: 30000,
idleTimeoutMillis: 30000
}
};
MySQL Configuration
const config = {
type: 'mysql',
host: 'localhost',
port: 3306,
database: 'myapp',
username: 'root',
password: 'password',
pool: {
min: 2,
max: 10,
acquireTimeoutMillis: 30000
}
};
MongoDB Configuration
const config = {
type: 'mongodb',
host: 'localhost',
port: 27017,
database: 'myapp',
username: 'user',
password: 'pass',
pool: {
min: 2,
max: 10
}
};
ORM Models
Defining Models
import { createModel } from '@coherent/database';
const User = createModel('User', {
tableName: 'users',
fillable: ['name', 'email', 'password', 'age'],
hidden: ['password'],
casts: {
age: 'number',
active: 'boolean'
},
validationRules: {
name: { required: true, minLength: 2 },
email: { required: true, email: true, unique: true },
password: { required: true, minLength: 6 }
},
relationships: {
posts: { type: 'hasMany', model: 'Post', foreignKey: 'user_id' },
profile: { type: 'hasOne', model: 'Profile', foreignKey: 'user_id' }
}
}, db);
Model Operations
// Create
const user = await User.create({
name: 'John Doe',
email: 'john@example.com',
password: 'secret123'
});
// Find
const user = await User.find(1);
const user = await User.findByEmail('john@example.com');
// Update
user.fill({ name: 'John Smith' });
await user.save();
// Delete
await user.delete();
// Query
const activeUsers = await User.where('active', true)
.where('age', '>', 18)
.orderBy('created_at', 'DESC')
.limit(10)
.execute();
Relationships
// Get related models
const posts = await user.getRelation('posts');
const profile = await user.getRelation('profile');
// Eager loading (would be implemented in advanced version)
const usersWithPosts = await User.with(['posts', 'profile']).execute();
Query Builder
Basic Queries
import { QueryBuilder } from '@coherent/database';
const query = new QueryBuilder(db, 'users');
// SELECT queries
const users = await query
.select(['id', 'name', 'email'])
.where('active', true)
.where('age', '>', 18)
.orderBy('created_at', 'DESC')
.limit(10)
.execute();
// Single result
const user = await query
.where('email', 'john@example.com')
.first();
Complex Queries
// Joins
const postsWithAuthors = await new QueryBuilder(db, 'posts')
.select(['posts.*', 'users.name as author_name'])
.join('users', 'posts.user_id', '=', 'users.id')
.where('posts.published', true)
.execute();
// Subqueries and conditions
const activeUsersWithPosts = await new QueryBuilder(db, 'users')
.where('active', true)
.where(q => q
.where('age', '>', 18)
.orWhere('verified', true)
)
.whereIn('id', [1, 2, 3, 4, 5])
.execute();
// Aggregation
const stats = await new QueryBuilder(db, 'users')
.select(['COUNT(*) as total', 'AVG(age) as avg_age'])
.where('active', true)
.first();
INSERT, UPDATE, DELETE
// Insert
await new QueryBuilder(db, 'users')
.insert({
name: 'Jane Doe',
email: 'jane@example.com'
})
.execute();
// Bulk insert
await new QueryBuilder(db, 'users')
.insert([
{ name: 'User 1', email: 'user1@example.com' },
{ name: 'User 2', email: 'user2@example.com' }
])
.execute();
// Update
await new QueryBuilder(db, 'users')
.update({ active: false })
.where('last_login', '<', '2023-01-01')
.execute();
// Delete
await new QueryBuilder(db, 'users')
.delete()
.where('active', false)
.execute();
Migrations
Creating Migrations
import { Migration } from '@coherent/database';
const migration = new Migration(db, {
directory: './migrations'
});
// Create migration file
await migration.create('create_users_table');
Migration File Example
// migrations/20231201000001_create_users_table.js
export async function up(schema) {
await schema.createTable('users', (table) => {
table.id();
table.string('name').notNull();
table.string('email').unique().notNull();
table.string('password').notNull();
table.integer('age');
table.boolean('active').default(true);
table.timestamps();
});
}
export async function down(schema) {
await schema.dropTable('users');
}
Running Migrations
import { runMigrations, rollbackMigrations } from '@coherent/database';
// Run pending migrations
const applied = await runMigrations(db);
// Rollback last batch
const rolledBack = await rollbackMigrations(db, 1);
// Check migration status
const migration = new Migration(db);
const status = await migration.status();
Router Integration
Database Middleware
import { SimpleRouter } from '@coherent/api';
import { withDatabase, withTransaction, withModel } from '@coherent/database';
const router = new SimpleRouter();
// Add database to all routes
router.use(withDatabase(db));
// Routes now have access to req.db, req.query, req.transaction
router.get('/users', async (req, res) => {
const users = await req.db.query('SELECT * FROM users');
res.json(users.rows);
});
Model Binding Middleware
// Automatically load model by route parameter
router.get('/users/:id', withModel(User), async (req, res) => {
// req.user contains the loaded User model
res.json(req.user.toJSON());
});
// Custom parameter and request key
router.get('/posts/:postId', withModel(Post, 'postId', 'post'), async (req, res) => {
res.json(req.post.toJSON());
});
Transaction Middleware
// Wrap entire route in transaction
router.post('/transfer', withTransaction(db), async (req, res) => {
const { fromId, toId, amount } = req.body;
// All operations use req.tx and are automatically committed/rolled back
await req.tx.query('UPDATE accounts SET balance = balance - ? WHERE id = ?', [amount, fromId]);
await req.tx.query('UPDATE accounts SET balance = balance + ? WHERE id = ?', [amount, toId]);
res.json({ success: true });
});
Pagination Middleware
router.get('/users', withPagination({ defaultLimit: 20 }), async (req, res) => {
const users = await User.query()
.limit(req.pagination.limit)
.offset(req.pagination.offset)
.execute();
res.json({
data: users.rows,
pagination: req.pagination
});
});
Transactions
Manual Transactions
const tx = await db.transaction();
try {
await tx.query('INSERT INTO users (name) VALUES (?)', ['John']);
await tx.query('INSERT INTO profiles (user_id) VALUES (?)', [userId]);
await tx.commit();
} catch (error) {
await tx.rollback();
throw error;
}
Transaction Options
// With isolation level
const tx = await db.transaction({
isolationLevel: 'READ COMMITTED',
readOnly: false
});
Connection Pooling
Connection pooling is automatically handled by the database adapters:
const config = {
type: 'postgresql',
// ... other config
pool: {
min: 2, // Minimum connections
max: 10, // Maximum connections
acquireTimeoutMillis: 30000, // Timeout to acquire connection
createTimeoutMillis: 30000, // Timeout to create connection
destroyTimeoutMillis: 5000, // Timeout to destroy connection
idleTimeoutMillis: 30000, // Idle timeout
reapIntervalMillis: 1000, // Cleanup interval
createRetryIntervalMillis: 200 // Retry interval
}
};
Validation
Built-in Validators
const validationRules = {
name: {
required: true,
minLength: 2,
maxLength: 100
},
email: {
required: true,
email: true,
unique: true
},
age: {
min: 0,
max: 120
},
password: {
required: true,
minLength: 6,
validator: async (value, model) => {
// Custom validation
if (!/[A-Z]/.test(value)) {
return 'Password must contain uppercase letter';
}
return true;
}
}
};
Custom Validation
const User = createModel('User', {
// ... other config
validationRules: {
email: {
required: true,
validator: async (email, model) => {
if (!email.includes('@')) {
return 'Invalid email format';
}
// Check uniqueness
const existing = await User.where('email', email)
.where('id', '!=', model.getAttribute('id'))
.exists();
if (existing) {
return 'Email already exists';
}
return true;
}
}
}
}, db);
Type Casting
const User = createModel('User', {
casts: {
age: 'number',
active: 'boolean',
metadata: 'json',
tags: 'array',
created_at: 'date'
}
}, db);
// Values are automatically cast
const user = new User({
age: '25', // Becomes number 25
active: 'true', // Becomes boolean true
metadata: '{"key": "value"}', // Becomes object
created_at: '2023-12-01' // Becomes Date object
});
Utilities
Health Checks
import { checkDatabaseHealth } from '@coherent/database';
const health = await checkDatabaseHealth(db);
console.log(`Database is ${health.status}`);
Batch Operations
import { batchOperations } from '@coherent/database';
const operations = [
{ sql: 'INSERT INTO users (name) VALUES (?)', params: ['John'] },
{ sql: 'INSERT INTO users (name) VALUES (?)', params: ['Jane'] }
];
const results = await batchOperations(db, operations, {
useTransaction: true,
continueOnError: false
});
Schema Documentation
import { generateSchemaDocs } from '@coherent/database';
const docs = await generateSchemaDocs(db, {
includeIndexes: true,
includeRelationships: true
});
Error Handling
try {
await user.save();
} catch (error) {
if (error.message.includes('Validation failed')) {
// Handle validation errors
console.log('Validation errors:', user.errors);
} else {
// Handle other database errors
console.error('Database error:', error.message);
}
}
Performance Tips
- Use Connection Pooling: Configure appropriate pool sizes for your workload
- Index Your Queries: Add database indexes for frequently queried columns
- Batch Operations: Use batch operations for multiple inserts/updates
- Limit Results: Always use
limit()
for large datasets - Use Transactions: Group related operations in transactions
- Monitor Queries: Enable debug mode to monitor query performance
MongoDB Specifics
For MongoDB, the query interface is adapted to use MongoDB operations:
// MongoDB operations
await db.query('find', ['users', { active: true }]);
await db.query('insertOne', ['users', { name: 'John' }]);
await db.query('updateOne', ['users', { _id: userId }, { $set: { name: 'Jane' } }]);
await db.query('deleteOne', ['users', { _id: userId }]);
await db.query('aggregate', ['users', [{ $match: { active: true } }]]);
Best Practices
- Always Use Migrations: Never modify database schema directly
- Validate Input: Use model validation rules consistently
- Handle Errors: Implement proper error handling for database operations
- Use Transactions: For operations that must succeed or fail together
- Monitor Performance: Track query performance and connection pool usage
- Secure Credentials: Never hardcode database credentials
- Test Thoroughly: Write tests for all database operations
Example Application
See examples/database-usage.js
for a complete example application demonstrating all database features including models, relationships, migrations, and router integration.