🔥告别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文件默认存储在项目根目录,生产环境请考虑数据库文件的安全存储。

相关推荐
鹏多多7 小时前
Vue3响应式原理Proxy的深度剖析
前端·javascript·vue.js
不可能的是7 小时前
深度解析:Sass-loader Legacy API 警告的前世今生与完美解决方案
前端·javascript
情绪的稳定剂_精神的锚7 小时前
VSCODE开发一个代码规范的插件入门
前端
养老不躺平7 小时前
关于nest项目打包
前端·javascript
Mike_jia8 小时前
uuWAF:开源Web应用防火墙新标杆——从工业级防护到智能防御实战解析
前端
掘金安东尼8 小时前
Chrome 17 岁了——我们的浏览器简史
前端·javascript·github
袁煦丞8 小时前
群晖NAS FTP远程文件仓库全球访问:cpolar内网穿透实验室第524个成功挑战
前端·程序员·远程工作
前端小巷子8 小时前
JS 打造动态表格
前端·javascript·面试
excel8 小时前
从卷积到全连接:用示例理解 CNN 的分层
前端