Database Package
A powerful database abstraction layer providing seamless driver switching between SQLite, MySQL, and PostgreSQL, built on top of bun-query-builder.
Installation
bun add @stacksjs/database
Basic Usage
import { db, Database, createSqliteDatabase } from '@stacksjs/database'
const users = await db.selectFrom('users')
.where('active', '=', true)
.get()
const customDb = new Database({
driver: 'postgres',
connection: {
database: 'myapp',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'secret'
}
})
Configuration
Environment Variables
DB_CONNECTION=sqlite
DB_DATABASE=database/stacks.sqlite
# For MySQL
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=stacks
DB_USERNAME=root
DB_PASSWORD=
# For PostgreSQL
DB_CONNECTION=postgres
DB_HOST=127.0.0.1
DB_PORT=5432
DB_DATABASE=stacks
DB_USERNAME=postgres
DB_PASSWORD=secret
Programmatic Configuration
import { Database, createDatabase } from '@stacksjs/database'
const sqliteDb = new Database({
driver: 'sqlite',
connection: { database: 'database/app.sqlite' }
})
const mysqlDb = new Database({
driver: 'mysql',
connection: {
database: 'myapp',
host: 'localhost',
port: 3306,
username: 'root',
password: 'secret'
}
})
const postgresDb = new Database({
driver: 'postgres',
connection: {
database: 'myapp',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'secret'
}
})
Database Options
const db = new Database({
driver: 'sqlite',
connection: { database: 'app.sqlite' },
verbose: true,
timestamps: {
createdAt: 'created_at',
updatedAt: 'updated_at',
defaultOrderColumn: 'created_at'
},
softDeletes: {
enabled: true,
column: 'deleted_at',
defaultFilter: true
},
hooks: {
beforeCreate: async (data) => { /_ ... _/ },
afterCreate: async (result) => { /_ ... _/ }
}
})
Query Builder
Select Queries
const users = await db.selectFrom('users').selectAll().execute()
const users = await db.selectFrom('users')
.select(['id', 'name', 'email'])
.execute()
const users = await db.selectFrom('users')
.select(['id', 'name as userName'])
.execute()
const cities = await db.selectFrom('users')
.distinct()
.select('city')
.execute()
Where Clauses
const users = await db.selectFrom('users')
.where('status', '=', 'active')
.execute()
const users = await db.selectFrom('users')
.where('status', '=', 'active')
.where('role', '=', 'admin')
.execute()
const users = await db.selectFrom('users')
.where('role', '=', 'admin')
.orWhere('role', '=', 'moderator')
.execute()
const users = await db.selectFrom('users')
.whereIn('id', [1, 2, 3])
.execute()
const unverified = await db.selectFrom('users')
.whereNull('email_verified_at')
.execute()
const verified = await db.selectFrom('users')
.whereNotNull('email_verified_at')
.execute()
const users = await db.selectFrom('users')
.whereBetween('age', 18, 65)
.execute()
const users = await db.selectFrom('users')
.where('name', 'like', '%john%')
.execute()
Joins
const posts = await db.selectFrom('posts')
.innerJoin('users', 'posts.user_id', 'users.id')
.select(['posts._', 'users.name as author_name'])
.execute()
const posts = await db.selectFrom('posts')
.leftJoin('comments', 'posts.id', 'comments.post_id')
.selectAll()
.execute()
const posts = await db.selectFrom('posts')
.rightJoin('users', 'posts.user_id', 'users.id')
.selectAll()
.execute()
Ordering and Grouping
const users = await db.selectFrom('users')
.orderBy('created_at', 'desc')
.execute()
const users = await db.selectFrom('users')
.orderBy('last_name', 'asc')
.orderBy('first_name', 'asc')
.execute()
const stats = await db.selectFrom('orders')
.select(['status', 'count(_) as total'])
.groupBy('status')
.execute()
const stats = await db.selectFrom('orders')
.select(['user_id', 'sum(total) as total_spent'])
.groupBy('user_id')
.having('total_spent', '>', 1000)
.execute()
Pagination and Limits
const users = await db.selectFrom('users')
.limit(10)
.offset(20)
.execute()
const result = await db.selectFrom('users')
.paginate(15, 1)
console.log(result.data)
console.log(result.total)
console.log(result.perPage)
console.log(result.currentPage)
console.log(result.lastPage)
Aggregates
const count = await db.selectFrom('users')
.count()
.executeTakeFirst()
const total = await db.selectFrom('orders')
.sum('amount')
.executeTakeFirst()
const avg = await db.selectFrom('products')
.avg('price')
.executeTakeFirst()
const minPrice = await db.selectFrom('products').min('price').executeTakeFirst()
const maxPrice = await db.selectFrom('products').max('price').executeTakeFirst()
Insert Queries
const result = await db.insertInto('users')
.values({
name: 'John Doe',
email: 'john@example.com'
})
.execute()
const [user] = await db.insertInto('users')
.values({ name: 'John', email: 'john@test.com' })
.returning('id')
.execute()
await db.insertInto('users')
.values([
{ name: 'User 1', email: 'user1@test.com' },
{ name: 'User 2', email: 'user2@test.com' },
{ name: 'User 3', email: 'user3@test.com' }
])
.execute()
await db.insertInto('users')
.values({ name: 'John', email: 'john@test.com' })
.onConflict()
.doNothing()
.execute()
await db.insertInto('users')
.values({ id: 1, name: 'John', email: 'john@test.com' })
.onConflict('id')
.doUpdateSet({ name: 'John Updated' })
.execute()
Update Queries
await db.updateTable('users')
.set({ status: 'inactive' })
.where('id', '=', 1)
.execute()
await db.updateTable('users')
.set({
name: 'Jane Doe',
updated_at: new Date()
})
.where('id', '=', 1)
.execute()
await db.updateTable('products')
.set({ stock: sql`stock - 1` })
.where('id', '=', 1)
.execute()
Delete Queries
await db.deleteFrom('users')
.where('status', '=', 'inactive')
.execute()
await db.deleteFrom('temp_logs').execute()
await db.truncateTable('sessions').execute()
Migrations
Creating Migrations
import { Migration } from '@stacksjs/database'
export default class CreateUsersTable extends Migration {
async up() {
await this.schema.createTable('users', (table) => {
table.id()
table.string('name')
table.string('email').unique()
table.string('password')
table.boolean('is_active').default(true)
table.timestamps()
})
}
async down() {
await this.schema.dropTable('users')
}
}
Column Types
table.id()
table.uuid('id').primary()
table.string('name', 255)
table.text('description')
table.integer('age')
table.bigInteger('views')
table.float('price')
table.decimal('amount', 10, 2)
table.boolean('active')
table.date('birth_date')
table.datetime('published_at')
table.timestamp('created_at')
table.json('metadata')
table.binary('data')
Column Modifiers
table.string('email')
.unique()
.nullable()
.default('default')
.references('id', 'roles')
table.timestamps()
table.softDeletes()
Running Migrations
buddy migrate
buddy migrate:rollback
buddy migrate:fresh
buddy migrate:status
Seeding
Creating Seeders
import { Seeder } from '@stacksjs/database'
export default class UserSeeder extends Seeder {
async run() {
await this.db.insertInto('users').values([
{ name: 'Admin', email: 'admin@example.com', role: 'admin' },
{ name: 'User', email: 'user@example.com', role: 'user' }
]).execute()
}
}
Using Factories
import { Seeder, factory } from '@stacksjs/database'
import { faker } from '@stacksjs/faker'
export default class UserSeeder extends Seeder {
async run() {
await factory('users', 50, () => ({
name: faker.person.fullName(),
email: faker.internet.email(),
created_at: faker.date.past()
}))
}
}
Running Seeders
buddy db:seed
buddy db:seed --class=UserSeeder
Transactions
import { db, transaction } from '@stacksjs/database'
await transaction(async (trx) => {
await trx.insertInto('orders')
.values({ user_id: 1, total: 100 })
.execute()
await trx.updateTable('users')
.set({ balance: sql`balance - 100` })
.where('id', '=', 1)
.execute()
})
const trx = await db.transaction()
try {
await trx.insertInto('orders').values({ /_ ... _/ }).execute()
await trx.commit()
} catch (error) {
await trx.rollback()
throw error
}
Raw Queries
import { db, sql } from '@stacksjs/database'
const users = await db.raw('SELECT _ FROM users WHERE status = ?', ['active'])
const users = await db.selectFrom('users')
.select([
'name',
sql`CONCAT(first_name, ' ', last_name) as full_name`
])
.execute()
const users = await db.selectFrom('users')
.where(sql`YEAR(created_at) = ${2024}`)
.execute()
Driver Switching
const database = new Database({
driver: 'sqlite',
connection: { database: 'app.sqlite' }
})
database.switchDriver('postgres', {
database: 'myapp',
host: 'localhost',
port: 5432,
username: 'postgres',
password: 'secret'
})
Connection Management
console.log(database.driver)
console.log(database.isInitialized)
database.close()
database.initialize()
Query Logging
import { enableQueryLogging, disableQueryLogging } from '@stacksjs/database'
enableQueryLogging()
await db.selectFrom('users').execute()
disableQueryLogging()
Edge Cases
Handling Connection Errors
import { Database } from '@stacksjs/database'
try {
const db = new Database({
driver: 'postgres',
connection: {
host: 'invalid-host',
database: 'myapp'
}
})
await db.query.selectFrom('users').execute()
} catch (error) {
if (error.code === 'ECONNREFUSED') {
console.error('Database connection refused')
}
}
Handling Large Datasets
const stream = db.selectFrom('logs')
.where('created_at', '>', '2024-01-01')
.stream()
for await (const row of stream) {
processRow(row)
}
Concurrent Connections
API Reference
Database Class
| Method | Description |
query | Get query builder instance |
driver | Get current driver name |
connection | Get connection config |
isInitialized | Check initialization state |
initialize() | Initialize connection |
switchDriver() | Switch database driver |
close() | Close connection |
Factory Functions
| Function | Description |
createDatabase(options) | Create database with options |
createSqliteDatabase(path) | Create SQLite database |
createPostgresDatabase(config) | Create PostgreSQL database |
createMysqlDatabase(config) | Create MySQL database |
Database.fromConfig(config) | Create from Stacks config |
Database.fromEnv() | Create from environment |
Query Builder Methods
| Method | Description |
selectFrom(table) | Start select query |
insertInto(table) | Start insert query |
updateTable(table) | Start update query |
deleteFrom(table) | Start delete query |
where() | Add where clause |
orderBy() | Add ordering |
limit() | Limit results |
offset() | Skip results |
join() | Add join |
groupBy() | Group results |
having() | Having clause |