你可以通过使用 routes 属性(用于静态路径、参数和通配符)或使用 fetch 方法处理未匹配的请求,来向 Bun.serve() 添加路由。
Bun.serve() 的路由构建在 uWebSocket 的 基于树的方法 之上,添加了 SIMD 加速的路由参数解码 和 JavaScriptCore 结构缓存,以推动现代硬件允许的性能极限。
基本设置
Bun.serve({
routes: {
"/": () => new Response("Home"),
"/api": () => Response.json({ success: true }),
"/users": async () => Response.json({ users: [] }),
},
fetch() {
return new Response("Unmatched route");
},
});Bun.serve() 中的路由接收 BunRequest(扩展了 Request)并返回 Response 或 Promise<Response>。这使得发送和接收 HTTP 请求使用相同的代码更容易。
// 简化以便简洁
interface BunRequest<T extends string> extends Request {
params: Record<T, string>;
readonly cookies: CookieMap;
}异步路由
Async/await
你可以在路由处理程序中使用 async/await 来返回 Promise<Response>。
import { sql, serve } from "bun";
serve({
port: 3001,
routes: {
"/api/version": async () => {
const [version] = await sql`SELECT version()`;
return Response.json(version);
},
},
});Promise
你也可以从路由处理程序返回 Promise<Response>。
import { sql, serve } from "bun";
serve({
routes: {
"/api/version": () => {
return new Promise(resolve => {
setTimeout(async () => {
const [version] = await sql`SELECT version()`;
resolve(Response.json(version));
}, 100);
});
},
},
});路由优先级
路由按特异性顺序匹配:
- 精确路由(
/users/all) - 参数路由(
/users/:id) - 通配符路由(
/users/*) - 全局捕获所有(
/*)
Bun.serve({
routes: {
// 最具体的在前
"/api/users/me": () => new Response("Current user"),
"/api/users/:id": req => new Response(`User ${req.params.id}`),
"/api/*": () => new Response("API catch-all"),
"/*": () => new Response("Global catch-all"),
},
});类型安全的路由参数
当作为字符串字面量传递时,TypeScript 会解析路由参数,因此你的编辑器在访问 request.params 时会显示自动完成。
import type { BunRequest } from "bun";
Bun.serve({
routes: {
// 当作为字符串字面量传递时,TypeScript 知道 params 的形状
"/orgs/:orgId/repos/:repoId": req => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
"/orgs/:orgId/repos/:repoId/settings": (
// 可选:你可以显式传递类型给 BunRequest
req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
) => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
},
});百分比编码的路由参数值会自动解码。支持 Unicode 字符。无效的 unicode 会被替换为 unicode 替换字符 ``。
静态响应
路由也可以是 Response 对象(不带处理程序函数)。Bun.serve() 针对零分配调度进行了优化——非常适合健康检查、重定向和固定内容:
Bun.serve({
routes: {
// 健康检查
"/health": new Response("OK"),
"/ready": new Response("Ready", {
headers: {
// 传递自定义头
"X-Ready": "1",
},
}),
// 重定向
"/blog": Response.redirect("https://bun.com/blog"),
// API 响应
"/api/config": Response.json({
version: "1.0.0",
env: "production",
}),
},
});静态响应在初始化后不会分配额外的内存。你通常可以期望至少 15% 的性能提升,相比手动返回 Response 对象。
静态路由响应在服务器的生命周期内缓存。要重新加载静态路由,调用 server.reload(options)。
文件响应与静态响应
在路由中提供文件时,根据你是缓冲文件内容还是直接提供,有两种不同的行为:
Bun.serve({
routes: {
// 静态路由 - 内容在启动时缓冲到内存中
"/logo.png": new Response(await Bun.file("./logo.png").bytes()),
// 文件路由 - 内容在每次请求时从文件系统读取
"/download.zip": new Response(Bun.file("./download.zip")),
},
});静态路由(new Response(await file.bytes()))在启动时将内容缓冲到内存中:
- 零文件系统 I/O - 内容完全从内存提供
- ETag 支持 - 自动生成和验证 ETags 用于缓存
- If-None-Match - 当客户端 ETag 匹配时返回
304 Not Modified - 无 404 处理 - 丢失的文件导致启动错误,而不是运行时 404
- 内存使用 - 完整文件内容存储在 RAM 中
- 最适合:小型静态资源、API 响应、频繁访问的文件
文件路由(new Response(Bun.file(path)))每次请求时从文件系统读取:
- 文件系统读取 在每次请求时 - 检查文件存在性并读取内容
- 内置 404 处理 - 如果文件不存在或变得不可访问则返回
404 Not Found - Last-Modified 支持 - 使用文件修改时间用于
If-Modified-Since头 - If-Modified-Since - 当文件自客户端缓存版本以来未更改时返回
304 Not Modified - 范围请求支持 - 自动处理带有
Content-Range头的部分内容请求 - 流式传输 - 使用带有背压处理的缓冲读取器以实现高效的内存使用
- 内存高效 - 仅在传输期间缓冲小块,而不是整个文件
- 最适合:大文件、动态内容、用户上传、频繁更改的文件
流式传输文件
要流式传输文件,返回一个带有 BunFile 对象作为主体的 Response 对象。
Bun.serve({
fetch(req) {
return new Response(Bun.file("./hello.txt"));
},
});你可以使用 Bun.file 对象上的 slice(start, end) 方法发送文件的一部分。这会自动在 Response 对象上设置 Content-Range 和 Content-Length 头。
Bun.serve({
fetch(req) {
// 解析 `Range` 头
const [start = 0, end = Infinity] = req.headers
.get("Range") // Range: bytes=0-100
.split("=") // ["Range: bytes", "0-100"]
.at(-1) // "0-100"
.split("-") // ["0", "100"]
.map(Number); // [0, 100]
// 返回文件的切片
const bigFile = Bun.file("./big-video.mp4");
return new Response(bigFile.slice(start, end));
},
});fetch 请求处理程序
fetch 处理程序处理未被任何路由匹配的传入请求。它接收 Request 对象并返回 Response 或 Promise<Response>。
Bun.serve({
fetch(req) {
const url = new URL(req.url);
if (url.pathname === "/") return new Response("Home page!");
if (url.pathname === "/blog") return new Response("Blog!");
return new Response("404!");
},
});fetch 处理程序支持 async/await:
import { sleep, serve } from "bun";
serve({
async fetch(req) {
const start = performance.now();
await sleep(10);
const end = performance.now();
return new Response(`Slept for ${end - start}ms`);
},
});也支持基于 Promise 的响应:
Bun.serve({
fetch(req) {
// 将请求转发到另一个服务器。
return fetch("https://example.com");
},
});你也可以从 fetch 处理程序访问 Server 对象。它是传递给 fetch 函数的第二个参数。
// `server` 作为第二个参数传递给 `fetch`。
const server = Bun.serve({
fetch(req, server) {
const ip = server.requestIP(req);
return new Response(`你的 IP 是 ${ip.address}`);
},
});