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 吗?或者已经在用了,踩过什么坑?评论区聊聊。
资源链接: