基本設置
const server = Bun.serve({
// `routes` 需要 Bun v1.2.3+
routes: {
// 靜態路由
"/api/status": new Response("OK"),
// 動態路由
"/users/:id": req => {
return new Response(`你好 用戶 ${req.params.id}!`);
},
// 每個 HTTP 方法的處理程序
"/api/posts": {
GET: () => new Response("列出帖子"),
POST: async req => {
const body = await req.json();
return Response.json({ created: true, ...body });
},
},
// 通配符路由,匹配所有以 "/api/" 開頭且未被其他匹配的路由
"/api/*": Response.json({ message: "未找到" }, { status: 404 }),
// 從 /blog/hello 重定向到 /blog/hello/world
"/blog/hello": Response.redirect("/blog/hello/world"),
// 通過懶加載到內存來提供文件
"/favicon.ico": Bun.file("./favicon.ico"),
},
// (可選)未匹配路由的回退:
// 如果 Bun 版本 < 1.2.3 則需要
fetch(req) {
return new Response("未找到", { status: 404 });
},
});
console.log(`服務器運行在 ${server.url}`);HTML 導入
Bun 支持直接將 HTML 文件導入到服務器代碼中,實現同時包含服務器端和客戶端代碼的全棧應用程序。HTML 導入有兩種模式:
開發模式(bun --hot): 資源在運行時按需打包,實現熱模塊替換(HMR),提供快速、迭代的開發體驗。當你更改前端代碼時,瀏覽器會自動更新而無需完全重新加載頁面。
生產模式(bun build): 當使用 bun build --target=bun 構建時,import index from "./index.html" 語句解析為包含所有打包客戶端資源的預構建清單對象。Bun.serve 使用此清單來提供優化的資源,零運行時打包開銷。這非常適合部署到生產環境。
import myReactSinglePageApp from "./index.html";
Bun.serve({
routes: {
"/": myReactSinglePageApp,
},
});HTML 導入不僅僅是提供 HTML——它是一個使用 Bun 的 打包器、JavaScript 轉譯器和 CSS 解析器構建的全功能前端打包器、轉譯器和工具包。你可以使用它來構建具有 React、TypeScript、Tailwind CSS 等的全功能前端。
有關使用 HTML 導入構建全棧應用程序的完整指南,包括詳細示例和最佳實踐,請參閱 /docs/bundler/fullstack。
配置
更改 port 和 hostname
要配置服務器監聽的端口和主機名,在選項對象中設置 port 和 hostname。
Bun.serve({
port: 8080, // 默認為 $BUN_PORT, $PORT, $NODE_PORT 否則為 3000
hostname: "mydomain.com", // 默認為 "0.0.0.0"
fetch(req) {
return new Response("404!");
},
});要隨機選擇可用端口,將 port 設置為 0。
const server = Bun.serve({
port: 0, // 隨機端口
fetch(req) {
return new Response("404!");
},
});
// server.port 是隨機選擇的端口
console.log(server.port);你可以通過訪問服務器對象上的 port 屬性或訪問 url 屬性來查看選擇的端口。
console.log(server.port); // 3000
console.log(server.url); // http://localhost:3000配置默認端口
Bun 支持多個選項和環境變量來配置默認端口。當未設置 port 選項時使用默認端口。
--portCLI 標志
bun --port=4002 server.tsBUN_PORT環境變量
BUN_PORT=4002 bun server.tsPORT環境變量
PORT=4002 bun server.tsNODE_PORT環境變量
NODE_PORT=4002 bun server.tsUnix 域套接字
要監聽 Unix 域套接字,傳遞 unix 選項和套接字路徑。
Bun.serve({
unix: "/tmp/my-socket.sock", // 套接字路徑
fetch(req) {
return new Response(`404!`);
},
});抽象命名空間套接字
Bun 支持 Linux 抽象命名空間套接字。要使用抽象命名空間套接字,在 unix 路徑前添加空字節前綴。
Bun.serve({
unix: "\0my-abstract-socket", // 抽象命名空間套接字
fetch(req) {
return new Response(`404!`);
},
});與 Unix 域套接字不同,抽象命名空間套接字不綁定到文件系統,並在套接字的最後一個引用關閉時自動刪除。
idleTimeout
要配置空閒超時,在 Bun.serve 中設置 idleTimeout 字段。
Bun.serve({
// 10 秒:
idleTimeout: 10,
fetch(req) {
return new Response("Bun!");
},
});這是連接在服務器關閉之前允許空閒的最長時間。如果沒有發送或接收數據,連接處於空閒狀態。
export default 語法
到目前為止,本頁的示例使用了顯式的 Bun.serve API。Bun 也支持另一種語法。
import { type Serve } from "bun";
export default {
fetch(req) {
return new Response("Bun!");
},
} satisfies Serve;不是將服務器選項傳遞給 Bun.serve,而是 export default 它。這個文件可以直接執行;當 Bun 看到包含 fetch 處理程序的 default 導出的文件時,它會在底層將其傳遞給 Bun.serve。
熱路由重新加載
使用 server.reload() 無需服務器重啟即可更新路由:
const server = Bun.serve({
routes: {
"/api/version": () => Response.json({ version: "1.0.0" }),
},
});
// 部署新路由而無需停機
server.reload({
routes: {
"/api/version": () => Response.json({ version: "2.0.0" }),
},
});服務器生命周期方法
server.stop()
要停止服務器接受新連接:
const server = Bun.serve({
fetch(req) {
return new Response("你好!");
},
});
// 優雅地停止服務器(等待進行中的請求)
await server.stop();
// 強制停止並關閉所有活動連接
await server.stop(true);默認情況下,stop() 允許進行中的請求和 WebSocket 連接完成。傳遞 true 立即終止所有連接。
server.ref() 和 server.unref()
控制服務器是否保持 Bun 進程活動:
// 如果服務器是唯一運行的內容,則不保持進程活動
server.unref();
// 恢復默認行為 - 保持進程活動
server.ref();server.reload()
無需重啟即可更新服務器的處理程序:
const server = Bun.serve({
routes: {
"/api/version": Response.json({ version: "v1" }),
},
fetch(req) {
return new Response("v1");
},
});
// 更新為新處理程序
server.reload({
routes: {
"/api/version": Response.json({ version: "v2" }),
},
fetch(req) {
return new Response("v2");
},
});這對於開發和熱重新加載很有用。只能更新 fetch、error 和 routes。
每請求控制
server.timeout(Request, seconds)
為單個請求設置自定義空閒超時:
const server = Bun.serve({
async fetch(req, server) {
// 為此請求設置 60 秒超時
server.timeout(req, 60);
// 如果他們花費超過 60 秒發送主體,請求將被中止
await req.text();
return new Response("完成!");
},
});傳遞 0 禁用請求的超時。
server.requestIP(Request)
獲取客戶端 IP 和端口信息:
const server = Bun.serve({
fetch(req, server) {
const address = server.requestIP(req);
if (address) {
return new Response(`客戶端 IP: ${address.address}, 端口:${address.port}`);
}
return new Response("未知客戶端");
},
});對於已關閉的請求或 Unix 域套接字返回 null。
服務器指標
server.pendingRequests 和 server.pendingWebSockets
使用內置計數器監控服務器活動:
const server = Bun.serve({
fetch(req, server) {
return new Response(
`活動請求數:${server.pendingRequests}\n` + `活動 WebSocket 數:${server.pendingWebSockets}`,
);
},
});server.subscriberCount(topic)
獲取 WebSocket 主題的訂閱者數量:
const server = Bun.serve({
fetch(req, server) {
const chatUsers = server.subscriberCount("chat");
return new Response(`${chatUsers} 用戶在聊天`);
},
websocket: {
message(ws) {
ws.subscribe("chat");
},
},
});基准測試
以下是 Bun 和 Node.js 實現的簡單 HTTP 服務器,對每個傳入的 Request 響應 Bun!。
Bun.serve({
fetch(req: Request) {
return new Response("Bun!");
},
port: 3000,
});require("http")
.createServer((req, res) => res.end("Bun!"))
.listen(8080);Bun.serve 服務器在 Linux 上可以處理比 Node.js 大約 2.5 倍更多的每秒請求數。
| 運行時 | 每秒請求數 |
|---|---|
| Node 16 | ~64,000 |
| Bun | ~160,000 |
實用示例:REST API
這是一個使用 Bun 路由器的基本數據庫支持的 REST API,零依賴:
import type { Post } from "./types.ts";
import { Database } from "bun:sqlite";
const db = new Database("posts.db");
db.exec(`
CREATE TABLE IF NOT EXISTS posts (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT NOT NULL
)
`);
Bun.serve({
routes: {
// 列出帖子
"/api/posts": {
GET: () => {
const posts = db.query("SELECT * FROM posts").all();
return Response.json(posts);
},
// 創建帖子
POST: async req => {
const post: Omit<Post, "id" | "created_at"> = await req.json();
const id = crypto.randomUUID();
db.query(
`INSERT INTO posts (id, title, content, created_at)
VALUES (?, ?, ?, ?)`,
).run(id, post.title, post.content, new Date().toISOString());
return Response.json({ id, ...post }, { status: 201 });
},
},
// 按 ID 獲取帖子
"/api/posts/:id": req => {
const post = db.query("SELECT * FROM posts WHERE id = ?").get(req.params.id);
if (!post) {
return new Response("未找到", { status: 404 });
}
return Response.json(post);
},
},
error(error) {
console.error(error);
return new Response("內部服務器錯誤", { status: 500 });
},
});export interface Post {
id: string;
title: string;
content: string;
created_at: string;
}參考
interface Server extends Disposable {
/**
* 停止服務器接受新連接。
* @param closeActiveConnections 如果為 true,立即終止所有連接
* @returns 當服務器停止時解析的 Promise
*/
stop(closeActiveConnections?: boolean): Promise<void>;
/**
* 無需重啟服務器即可更新處理程序。
* 只能更新 fetch 和 error 處理程序。
*/
reload(options: Serve): void;
/**
* 向運行的服務器發出請求。
* 用於測試或內部路由。
*/
fetch(request: Request | string): Response | Promise<Response>;
/**
* 將 HTTP 請求升級為 WebSocket 連接。
* @returns 升級成功為 true,失敗為 false
*/
upgrade<T = undefined>(
request: Request,
options?: {
headers?: Bun.HeadersInit;
data?: T;
},
): boolean;
/**
* 向訂閱主題的所有 WebSocket 客戶端發布消息。
* @returns 發送的字節數,如果丟棄則為 0,如果應用背壓則為 -1
*/
publish(
topic: string,
data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer,
compress?: boolean,
): ServerWebSocketSendStatus;
/**
* 獲取訂閱主題的 WebSocket 客戶端數量。
*/
subscriberCount(topic: string): number;
/**
* 獲取客戶端 IP 地址和端口。
* @returns 對於已關閉的請求或 Unix 套接字為 null
*/
requestIP(request: Request): SocketAddress | null;
/**
* 為請求設置自定義空閒超時。
* @param seconds 超時秒數,0 禁用
*/
timeout(request: Request, seconds: number): void;
/**
* 服務器運行時保持進程活動。
*/
ref(): void;
/**
* 如果服務器是唯一運行的內容,允許進程退出。
*/
unref(): void;
/** 進行中的 HTTP 請求數 */
readonly pendingRequests: number;
/** 活動 WebSocket 連接數 */
readonly pendingWebSockets: number;
/** 服務器 URL,包括協議、主機名和端口 */
readonly url: URL;
/** 服務器監聽的端口 */
readonly port: number;
/** 服務器綁定的主機名 */
readonly hostname: string;
/** 服務器是否處於開發模式 */
readonly development: boolean;
/** 服務器實例標識符 */
readonly id: string;
}
interface WebSocketHandler<T = undefined> {
/** 最大 WebSocket 消息大小(字節) */
maxPayloadLength?: number;
/** 應用背壓前排隊消息的字節數 */
backpressureLimit?: number;
/** 達到背壓限制時是否關閉連接 */
closeOnBackpressureLimit?: boolean;
/** 背壓緩解時調用 */
drain?(ws: ServerWebSocket<T>): void | Promise<void>;
/** 空閒超時前的秒數 */
idleTimeout?: number;
/** 啟用每消息 deflate 壓縮 */
perMessageDeflate?:
| boolean
| {
compress?: WebSocketCompressor | boolean;
decompress?: WebSocketCompressor | boolean;
};
/** 發送 ping 幀以保持連接活動 */
sendPings?: boolean;
/** 服務器是否接收自己發布的消息 */
publishToSelf?: boolean;
/** 連接打開時調用 */
open?(ws: ServerWebSocket<T>): void | Promise<void>;
/** 收到消息時調用 */
message(ws: ServerWebSocket<T>, message: string | Buffer): void | Promise<void>;
/** 連接關閉時調用 */
close?(ws: ServerWebSocket<T>, code: number, reason: string): void | Promise<void>;
/** 收到 ping 幀時調用 */
ping?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;
/** 收到 pong 幀時調用 */
pong?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;
}
interface TLSOptions {
/** 證書頒發機構鏈 */
ca?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;
/** 服務器證書 */
cert?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;
/** DH 參數文件路徑 */
dhParamsFile?: string;
/** 私鑰 */
key?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;
/** 減少 TLS 內存使用 */
lowMemoryMode?: boolean;
/** 私鑰密碼短語 */
passphrase?: string;
/** OpenSSL 選項標志 */
secureOptions?: number;
/** SNI 的服務器名稱 */
serverName?: string;
}