你可以通過使用 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}`);
},
});