🔥告别ORM臃肿!用Bun.js原生SQLite打造极致轻量级数据库层

还在为复杂的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的数据库封装方案,以其极简的配置、出色的性能、完整的类型支持,为前端开发者提供了一个全新的选择。

核心优势

  1. 🚀 零配置开箱即用
  2. 🛡️ 完整的TypeScript支持
  3. 📦 极致的轻量级体验
  4. ⚡ 原生性能优势
  5. 🔧 灵活的扩展能力

不要再被臃肿的ORM框架束缚了!尝试这个方案,你会发现数据库操作原来可以如此简单优雅。


*注:本文中的代码基于Bun.js 1.2+版本,建议使用最新版本获得最佳体验。SQLite文件默认存储在项目根目录,生产环境请考虑数据库文件的安全存储。

相关推荐
恋猫de小郭6 小时前
Flutter Zero 是什么?它的出现有什么意义?为什么你需要了解下?
android·前端·flutter
崔庆才丨静觅13 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby606113 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了13 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅14 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅14 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅14 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment14 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅15 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊15 小时前
jwt介绍
前端