还在为复杂的ORM配置头疼?这个Bun.js + SQLite方案让你的数据库操作简单到哭!
为什么选择Bun.js + SQLite?
作为一名前端开发者,你可能已经厌倦了各种臃肿的ORM框架和繁琐的数据库配置。Node.js + Sequelize?太重量级。Deno + PostgreSQL?配置复杂。是时候尝试一些更现代、更轻量的方案了!
Bun.js的出现彻底改变了游戏规则:内置的SQLite支持、原生TypeScript、惊人的性能表现。再加上SQLite的轻量级和零配置特性,这个组合简直就是前端开发者的梦想搭档!
今天我要分享的这个db.ts
封装,将让你体验到什么叫做"开箱即用"的数据库操作爽感。
核心特性深度解析
1. 智能表结构管理 🧠
typescript
// 自动创建表和添加缺失字段
createTableIfNotExists('users', {
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
name: 'TEXT NOT NULL',
email: 'TEXT UNIQUE',
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
});
// 初始化数据表
import { dbInstance } from "./db";
export function initDatabase() {
// 演示案例表
dbInstance.createTableIfNotExists('demos', {
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
title: 'TEXT NOT NULL',
description: 'TEXT',
category: 'TEXT NOT NULL',
max_access_count: 'INTEGER DEFAULT 0', // 0 = 无限
expires_at: 'TEXT', // ISO string
is_public: 'BOOLEAN DEFAULT 0', // 0=需鉴权, 1=公开
created_at: 'TEXT DEFAULT CURRENT_TIMESTAMP',
});
// 访问记录表(用于统计)
dbInstance.createTableIfNotExists('access_logs', {
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
// demo_id: 'TEXT NOT NULL',
demo_id: 'TEXT NOT NULL REFERENCES demos(id) ON DELETE CASCADE',
user_id: 'TEXT',
accessed_at: 'TEXT DEFAULT CURRENT_TIMESTAMP',
ip: 'TEXT',
user_agent: 'TEXT',
// FOREIGN_KEY: '(demo_id) REFERENCES demos(id) ON DELETE CASCADE',
});
// 用户表(简化版,实际可对接 Auth.js)
dbInstance.createTableIfNotExists('users', {
id: 'TEXT PRIMARY KEY',
name: 'TEXT',
email: 'TEXT UNIQUE',
role: 'TEXT DEFAULT \'guest\'', // 'admin', 'user', 'guest'
token: 'TEXT',
created_at: 'TEXT DEFAULT CURRENT_TIMESTAMP',
});
}
这个功能有多实用?想象一下:你修改了数据模型,不需要手动执行ALTER TABLE语句,不需要复杂的迁移工具。代码会自动检测并添加缺失的字段!
实现原理:
tableExists()
检查表是否存在columnExists()
通过SQLite的PRAGMA命令检查字段- 自动执行CREATE TABLE或ALTER TABLE
2. 类型安全的查询操作 🔒
typescript
// 查询所有用户
const users = dbInstance.query('SELECT * FROM users WHERE age > ?', [18]);
// 获取单条记录
const user = dbInstance.get('SELECT * FROM users WHERE id = ?', [1]);
// 执行更新操作
dbInstance.run('UPDATE users SET name = ? WHERE id = ?', ['李四', 1]);
得益于Bun.js的SQLQueryBindings
类型,所有参数都享受TypeScript的类型检查,避免SQL注入风险。
3. 强大的分页查询 📄
typescript
// 第二页,每页10条,按创建时间倒序
const result = dbInstance.paginate(
'users',
2,
10,
'status = ?',
['active'],
'created_at DESC'
);
console.log(result);
// 输出: { data: [...], total: 150, totalPages: 15 }
这个分页方法不仅返回当前页数据,还提供总记录数和总页数,完美满足前端分页需求。
4. 便捷的统计功能 📊
typescript
// 统计用户数据
const stats = dbInstance.stats('users', 'age', 'status = ?', ['active']);
console.log(stats);
// 输出: { count: 100, sum: 2500, avg: 25, max: 60, min: 18 }
一行代码搞定COUNT、SUM、AVG、MAX、MIN统计,数据分析从未如此简单!
完整使用示例 🚀
让我们看一个完整的用户管理系统示例:
typescript
// user.ts
interface User {
id?: number;
name: string;
email: string;
age: number;
status: 'active' | 'inactive';
}
class UserService {
// 初始化用户表
static initTable() {
dbInstance.createTableIfNotExists('users', {
id: 'INTEGER PRIMARY KEY AUTOINCREMENT',
name: 'TEXT NOT NULL',
email: 'TEXT UNIQUE NOT NULL',
age: 'INTEGER',
status: 'TEXT DEFAULT "active"',
created_at: 'DATETIME DEFAULT CURRENT_TIMESTAMP'
});
}
// 创建用户
static createUser(user: Omit<User, 'id'>) {
dbInstance.run(
'INSERT INTO users (name, email, age, status) VALUES (?, ?, ?, ?)',
[user.name, user.email, user.age, user.status]
);
}
// 获取用户列表
static getUsers(page: number = 1, pageSize: number = 10) {
return dbInstance.paginate('users', page, pageSize, '', [], 'id DESC');
}
// 根据邮箱查找用户
static findByEmail(email: string) {
return dbInstance.get(
'SELECT * FROM users WHERE email = ?',
[email]
) as User | null;
}
}
// 使用示例
UserService.initTable();
// 创建测试用户
UserService.createUser({
name: '张三',
email: 'zhangsan@example.com',
age: 25,
status: 'active'
});
// 分页查询用户
const usersPage = UserService.getUsers(1, 10);
console.log('第一页用户:', usersPage);
// 按邮箱查找
const user = UserService.findByEmail('zhangsan@example.com');
console.log('找到用户:', user);
性能优化建议 ⚡
1. 启用WAL模式
typescript
// 在创建数据库实例后添加
db.exec('PRAGMA journal_mode = WAL;');
db.exec('PRAGMA synchronous = NORMAL;');
WAL(Write-Ahead Logging)模式可以显著提升并发写入性能。
2. 使用预处理语句
对于频繁执行的查询,可以考虑缓存预处理语句:
typescript
class DB {
private preparedStatements = new Map<string, any>();
getPreparedStatement(sql: string) {
if (!this.preparedStatements.has(sql)) {
this.preparedStatements.set(sql, this.db.prepare(sql));
}
return this.preparedStatements.get(sql);
}
}
3. 批量操作优化
对于批量插入,使用事务可以大幅提升性能:
typescript
// 批量插入用户
function batchInsertUsers(users: User[]) {
dbInstance.run('BEGIN TRANSACTION');
try {
users.forEach(user => {
dbInstance.run(
'INSERT INTO users (name, email, age) VALUES (?, ?, ?)',
[user.name, user.email, user.age]
);
});
dbInstance.run('COMMIT');
} catch (error) {
dbInstance.run('ROLLBACK');
throw error;
}
}
完整db.ts
typescript
/**
* @description 数据库操作封装
* @author daoxin
* @date 2025/09/08 21:00
* @version v1.0
* @copyright Copyright (c) 2025
* @docs https://bun.net.cn/docs/api/sqlite
*
*/
import { Database, type SQLQueryBindings } from "bun:sqlite";
// 数据库文件路径(默认为当前目录下的 database.sqlite 文件)
const DB_FILE = Bun.env.DB_FILE || "database.sqlite";
// 创建或打开 SQLite 数据库
const db = new Database(DB_FILE, { create: true });
// 封装数据库操作的类
class DB {
private db: Database;
constructor() {
this.db = db;
}
// 检查表是否存在
private tableExists(tableName: string): boolean {
const result = this.db.query(
"SELECT name FROM sqlite_master WHERE type='table' AND name = ?;"
).get(tableName);
return !!result;
}
// 检查字段是否存在
private columnExists(tableName: string, columnName: string): boolean {
const result = this.db.query(
`PRAGMA table_info("${tableName}");`
).all() as Array<{ name: string }>;
return result.some((column) => column.name === columnName);
}
// 创建表(如果不存在)
createTableIfNotExists(tableName: string, columns: Record<string, string>): void {
if (!this.tableExists(tableName)) {
const columnDefinitions = Object.entries(columns)
.map(([name, type]) => `"${name}" ${type}`)
.join(', ');
const sql = `CREATE TABLE "${tableName}" (${columnDefinitions});`;
this.db.run(sql);
console.log(`表 ${tableName} 已创建。`);
} else {
console.log(`表 ${tableName} 已存在,检查字段...`);
this.addMissingColumns(tableName, columns);
}
}
// 添加缺失的字段
private addMissingColumns(tableName: string, columns: Record<string, string>): void {
for (const [columnName, columnType] of Object.entries(columns)) {
if (!this.columnExists(tableName, columnName)) {
const sql = `ALTER TABLE "${tableName}" ADD COLUMN "${columnName}" ${columnType};`;
this.db.run(sql);
console.log(`字段 ${columnName} 已添加到表 ${tableName}。`);
}
}
}
// 执行 SQL 查询
query(sql: string, params: SQLQueryBindings[] = []): unknown[] {
const stmt = this.db.query(sql);
return stmt.all(...params);
}
// 执行 SQL 插入、更新、删除操作
run(sql: string, params: SQLQueryBindings[] = []): void {
const stmt = this.db.prepare(sql);
stmt.run(...params);
}
// 获取单行数据
get(sql: string, params: SQLQueryBindings[] = []): unknown {
const stmt = this.db.query(sql);
return stmt.get(...params);
}
// 分页查询
paginate(
tableName: string,
page: number = 1,
pageSize: number = 10,
conditions: string = '',
params: SQLQueryBindings[] = [],
orderBy: string = 'id ASC'
): { data: unknown[]; total: number; totalPages: number } {
const offset = (page - 1) * pageSize;
// 查询数据
let querySql = `SELECT * FROM ${tableName}`;
if (conditions) {
querySql += ` WHERE ${conditions}`;
}
querySql += ` ORDER BY ${orderBy} LIMIT ${pageSize} OFFSET ${offset};`;
const data = this.query(querySql, params);
// 查询总记录数
let countSql = `SELECT COUNT(*) as total FROM ${tableName}`;
if (conditions) {
countSql += ` WHERE ${conditions}`;
}
const totalResult = this.get(countSql, params) as { total: number };
const total = totalResult.total;
const totalPages = Math.ceil(total / pageSize);
return { data, total, totalPages };
}
// 统计功能
stats(
tableName: string,
column: string,
conditions: string = '',
params: SQLQueryBindings[] = []
): { count: number; sum: number | null; avg: number | null; max: unknown; min: unknown } {
let statsSql = `
SELECT
COUNT(${column}) as count,
SUM(${column}) as sum,
AVG(${column}) as avg,
MAX(${column}) as max,
MIN(${column}) as min
FROM ${tableName}
`;
if (conditions) {
statsSql += ` WHERE ${conditions}`;
}
const result = this.get(statsSql, params) as {
count: number;
sum: number | null;
avg: number | null;
max: unknown;
min: unknown;
};
return {
count: result.count,
sum: result.sum !== null ? Number(result.sum) : null,
avg: result.avg !== null ? Number(result.avg) : null,
max: result.max,
min: result.min,
};
}
// 关闭数据库连接
close(): void {
this.db.close();
}
}
// 导出 DB 实例
export const dbInstance = new DB();
与传统方案的对比 📊
特性 | Bun.js + SQLite | Node.js + Sequelize | Deno + PostgreSQL |
---|---|---|---|
启动速度 | ⚡️ 极快 | 🐢 慢 | 🐢 慢 |
内存占用 | 🪶 轻量 | 🐘 重量 | 🐘 重量 |
配置复杂度 | ⭐ 零配置 | 🌟🌟🌟 复杂 | 🌟🌟🌟 复杂 |
开发体验 | 😊 优秀 | 😐 一般 | 😐 一般 |
类型支持 | 🔥 原生TS | ✅ 需要插件 | ✅ 需要插件 |
适用场景推荐 🎯
这个方案特别适合:
- 原型开发:快速验证想法,无需复杂数据库配置
- 小型项目:轻量级应用,不需要复杂的数据库功能
- 边缘计算:低资源环境下的数据存储
- 本地缓存:客户端数据持久化方案
- 测试环境:快速搭建测试数据库
总结 💡
这个基于Bun.js + SQLite的数据库封装方案,以其极简的配置、出色的性能、完整的类型支持,为前端开发者提供了一个全新的选择。
核心优势:
- 🚀 零配置开箱即用
- 🛡️ 完整的TypeScript支持
- 📦 极致的轻量级体验
- ⚡ 原生性能优势
- 🔧 灵活的扩展能力
不要再被臃肿的ORM框架束缚了!尝试这个方案,你会发现数据库操作原来可以如此简单优雅。
*注:本文中的代码基于Bun.js 1.2+版本,建议使用最新版本获得最佳体验。SQLite文件默认存储在项目根目录,生产环境请考虑数据库文件的安全存储。