10 分钟用 Bun + Hono + SQLite 跑通一个全栈 API

npm install 那根进度条走到一半,你打开手机刷了三条短视频,回来发现它还在装第 287 个包。

Node.js 生态就是这样------你要起一个带数据库的全栈项目,得先装 Node、装包管理器、装框架、装 ORM、装数据库驱动。等全部搞定,半小时过去了,你还一行业务代码没写。

Bun 做的事很简单:把这些全塞进一个二进制文件里。它既是运行时,也是包管理器,还内置了 SQLite。你装完 Bun,等于同时装好了 Node.js + npm + better-sqlite3 + jest + webpack + ts-node。 而且安装只要一行命令:

bash 复制代码
# Windows
powershell -c "irm bun.sh/install.ps1 | iex"

# macOS / Linux
curl -fsSL https://bun.sh/install | bash

今天我们用 Bun + Hono + SQLite 搭一个「前端工具收藏夹」API,10 分钟跑通增删改查+搜索,全程零额外依赖。


初始化项目

bash 复制代码
bun init

回车三下,项目骨架生成完毕。对比一下:

操作 npm bun
init ~3 秒 ~0.3 秒
install express ~8 秒 (189 个包) bun add hono ~1 秒 (3 个包)

包管理器这层 Bun 直接用自身二进制做符号链接,没有 node_modules 那一大坨,装包速度肉眼可见地快。


搭路由:为什么选 Hono

Express 太沉,Fastify 插件多,纯写 API Hono 最合适------它设计上对标 Web 标准 Request/Response,天生支持 async/await,而且类型推断会自动流到路由处理器里 ,不用额外装 @types/express

bash 复制代码
bun add hono

写第一个路由:

typescript 复制代码
// src/index.ts
import { Hono } from "hono";

const app = new Hono();

app.get("/", (c) => {
  return c.json({ message: "工具收藏夹 API 已就绪" });
});

// 路由写完后,底部导出 Bun 需要的格式
// export default { port: 3000, fetch: app.fetch };

Hono 的 c.json() 自动设置 Content-Type,不用手动 res.setHeader。这里的泛型推断也比你想象的聪明------后面接数据库查询时,你返回什么类型,TS 都能自动推导。


内置 SQLite:零依赖操作数据库

这是 Bun 最让我舒服的地方。bun:sqlite 是编译进运行时的,写法就是原生 SQL,没有 ORM 那一层的概念翻译成本。

typescript 复制代码
// src/db.ts
import { Database } from "bun:sqlite";

const db = new Database("tools.sqlite");

db.run(`
  CREATE TABLE IF NOT EXISTS tools (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    url TEXT NOT NULL,
    category TEXT NOT NULL DEFAULT '未分类',
    description TEXT,
    created_at TEXT DEFAULT (datetime('now','localtime'))
  )
`);

// 种子数据
const count = db.query("SELECT COUNT(*) as count FROM tools").get() as { count: number };
if (count.count === 0) {
  const insert = db.prepare("INSERT INTO tools (name, url, category, description) VALUES (?, ?, ?, ?)");
  db.transaction(() => {
    insert.run("Bun", "https://bun.sh", "运行时", "一站式 JS/TS 运行时");
    insert.run("Hono", "https://hono.dev", "框架", "轻量高性能 Web 框架");
    insert.run("Biome", "https://biomejs.dev", "工具链", "替代 ESLint + Prettier");
    insert.run("Drizzle", "https://orm.drizzle.team", "数据库", "TypeScript 优先的 ORM");
    insert.run("Tailwind CSS", "https://tailwindcss.com", "样式", "实用优先的 CSS 框架");
  })();
}

export { db };

bun:sqlite 不需要安装,import 直接用。db.transaction() 包裹批量操作,要么全成功要么全回滚,这比 Node.js 里手动开启/提交事务简洁太多。


写接口

回到 src/index.ts,顶部加上 db 的 import,然后把五个接口加到路由里。

typescript 复制代码
import { db } from "./db";
// ... 前面已有的 Hono 初始化代码

app.get("/tools", (c) => {
  const keyword = c.req.query("q");
  let tools;
  if (keyword) {
    tools = db
      .query(
        `SELECT * FROM tools
         WHERE name LIKE ? OR description LIKE ? OR category LIKE ?
         ORDER BY created_at DESC`
      )
      .all(`%${keyword}%`, `%${keyword}%`, `%${keyword}%`);
  } else {
    tools = db.query("SELECT * FROM tools ORDER BY created_at DESC").all();
  }
  return c.json({ data: tools, total: (tools as any[]).length });
});

搜索走 LIKE 模糊匹配,三个字段同时搜,够用。数据量大之后换全文索引也方便,SQL 不变。

bash 复制代码
curl http://localhost:3000/tools
curl "http://localhost:3000/tools?q=Bun"

获取单个工具

typescript 复制代码
app.get("/tools/:id", (c) => {
  const id = c.req.param("id");
  const tool = db.query("SELECT * FROM tools WHERE id = ?").get(id) as any;
  if (!tool) return c.json({ error: "未找到该工具" }, 404);
  return c.json({ data: tool });
});

新增工具

typescript 复制代码
app.post("/tools", async (c) => {
  const { name, url, category, description } = await c.req.json();
  if (!name || !url) {
    return c.json({ error: "name 和 url 不能为空" }, 400);
  }
  db.run(
    "INSERT INTO tools (name, url, category, description) VALUES (?, ?, ?, ?)",
    [name, url, category || "未分类", description || ""]
  );
  return c.json({ message: "添加成功" }, 201);
});

参数校验只做了最简单的,生产环境建议用 Zod。

bash 复制代码
curl -X POST http://localhost:3000/tools \
  -H "Content-Type: application/json" \
  -d '{"name":"Vite","url":"https://vitejs.dev","category":"构建工具","description":"下一代前端构建工具"}'

编辑工具

typescript 复制代码
app.put("/tools/:id", async (c) => {
  const id = c.req.param("id");
  const { name, url, category, description } = await c.req.json();
  const result = db.run(
    "UPDATE tools SET name = ?, url = ?, category = ?, description = ? WHERE id = ?",
    [name, url, category, description, id]
  );
  if (result.changes === 0) return c.json({ error: "未找到该工具" }, 404);
  return c.json({ message: "更新成功" });
});

删除工具

typescript 复制代码
app.delete("/tools/:id", (c) => {
  const id = c.req.param("id");
  const result = db.run("DELETE FROM tools WHERE id = ?", [id]);
  if (result.changes === 0) return c.json({ error: "未找到该工具" }, 404);
  return c.json({ message: "删除成功" });
});

五组接口写下来,没有一个 import 来自额外安装的包。JavaScript 原生 + Bun 内置 + Hono,就这么三层。


跑起来

src/index.ts 底部那行注释去掉,换成:

typescript 复制代码
export default { port: 3000, fetch: app.fetch };

bun run src/index.ts 启动,或者带热重载:

bash 复制代码
bun --watch src/index.ts

改代码秒级重启,跟 Vite 的前端体验一样顺滑。

Bun 还内置测试和打包:

bash 复制代码
bun test          # 跑测试,不需要 jest/vitest
bun build ./src/index.ts --outdir ./dist  # 打包成单文件可执行

你甚至可以 bun build --compile 把整个项目编译成一个独立的二进制文件,扔到服务器上直接运行。


总结

一个运行时搞定了运行时、包管理、数据库、测试、打包------这就是 Bun 给前端开发者的诚意。package.json 里除了 Hono 一个依赖,什么都没有,node_modules 只有 3 个文件夹,但一个完整的 CRUD API 已经跑起来了。


你现在会在生产环境用 Bun 吗?或者已经在用了,踩过什么坑?评论区聊聊。

资源链接:

相关推荐
JieE21220 小时前
Bun + TypeScript:下一代 JavaScript 全栈开发的正确打开方式
typescript·全栈·bun
不好听6131 天前
Bun vs Node.js:谁才是 TypeScript 的"亲爹"?
typescript·node.js·bun
拾年2751 天前
Bun:重新定义 JavaScript 运行时 - 为什么它可能是 Node.js 的终结者?
javascript·typescript·bun
mONESY1 天前
Bun+TS零配置极速实现大模型API请求
typescript·bun
孟陬20 天前
从 Claude Code 187 种说“正在处理”的方式看一流公司的用户体验
前端·claude·bun
mCell23 天前
从云相册的缩略图说起:Bun.Image 让我告别 sharp
javascript·图片资源·bun
带娃的IT创业者23 天前
Rewrite Bun in Rust:一次前端工具链的底层重构实践入门指南
前端·重构·rust·bun·运行时·前端工具链
子兮曰1 个月前
Bun v1.3.14 深度解析:Image API、HTTP/3、全局虚拟存储与五十项变革
前端·后端·bun
Bigger1 个月前
Bun 能上生产吗?我的实战结论
前端·node.js·bun