基本设置
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;
}