此页面主要介绍 Bun 原生的 Bun.serve
API。Bun 也实现了 fetch
和 Node.js 的 http
以及 https
模块。
这些模块已被重新实现,以使用 Bun 快速的内部 HTTP 基础设施。请随意直接使用这些模块;依赖于这些模块的框架(如 Express)应该可以开箱即用。有关细粒度的兼容性信息,请参阅运行时 > Node.js API。
要启动具有简洁 API 的高性能 HTTP 服务器,推荐的方法是 Bun.serve
。
Bun.serve()
使用 Bun.serve
在 Bun 中启动 HTTP 服务器。
Bun.serve({
// `routes` requires Bun v1.2.3+
routes: {
// Static routes
"/api/status": new Response("OK"),
// Dynamic routes
"/users/:id": req => {
return new Response(`Hello User ${req.params.id}!`);
},
// Per-HTTP method handlers
"/api/posts": {
GET: () => new Response("List posts"),
POST: async req => {
const body = await req.json();
return Response.json({ created: true, ...body });
},
},
// Wildcard route for all routes that start with "/api/" and aren't otherwise matched
"/api/*": Response.json({ message: "Not found" }, { status: 404 }),
// Redirect from /blog/hello to /blog/hello/world
"/blog/hello": Response.redirect("/blog/hello/world"),
// Serve a file by buffering it in memory
"/favicon.ico": new Response(await Bun.file("./favicon.ico").bytes(), {
headers: {
"Content-Type": "image/x-icon",
},
}),
},
// (optional) fallback for unmatched routes:
// Required if Bun's version < 1.2.3
fetch(req) {
return new Response("Not Found", { status: 404 });
},
});
路由
Bun.serve()
中的路由接收 BunRequest
(它扩展了 Request
)并返回 Response
或 Promise<Response>
。这使得为发送和接收 HTTP 请求使用相同的代码更加容易。
// Simplified for brevity
interface BunRequest<T extends string> extends Request {
params: Record<T, string>;
}
路由中的 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);
});
},
},
});
类型安全的路由参数
当以字符串字面量形式传递路由参数时,TypeScript 会解析路由参数,以便你的编辑器在访问 request.params
时显示自动完成。
import type { BunRequest } from "bun";
Bun.serve({
routes: {
// TypeScript knows the shape of params when passed as a string literal
"/orgs/:orgId/repos/:repoId": req => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
"/orgs/:orgId/repos/:repoId/settings": (
// optional: you can explicitly pass a type to BunRequest:
req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
) => {
const { orgId, repoId } = req.params;
return Response.json({ orgId, repoId });
},
},
});
百分比编码的路由参数值会自动解码。支持 Unicode 字符。无效的 Unicode 字符将被 Unicode 替换字符 &0xFFFD;
替换。
静态响应
路由也可以是 Response
对象(不带处理函数)。Bun.serve()
对其进行了优化,实现零分配调度 - 非常适合健康检查、重定向和固定内容
Bun.serve({
routes: {
// Health checks
"/health": new Response("OK"),
"/ready": new Response("Ready", {
headers: {
// Pass custom headers
"X-Ready": "1",
},
}),
// Redirects
"/blog": Response.redirect("https://bun.net.cn/blog"),
// API responses
"/api/config": Response.json({
version: "1.0.0",
env: "production",
}),
},
});
静态响应在初始化后不会分配额外的内存。通常,你可以预期比手动返回 Response
对象至少提高 15% 的性能。
静态路由响应在服务器对象的生命周期内被缓存。要重新加载静态路由,请调用 server.reload(options)
。
const server = Bun.serve({
static: {
"/api/time": new Response(new Date().toISOString()),
},
fetch(req) {
return new Response("404!");
},
});
// Update the time every second.
setInterval(() => {
server.reload({
static: {
"/api/time": new Response(new Date().toISOString()),
},
fetch(req) {
return new Response("404!");
},
});
}, 1000);
重新加载路由只会影响下一个请求。正在处理的请求将继续使用旧路由。在完成对旧路由的正在处理的请求后,旧路由将从内存中释放。
为了简化错误处理,静态路由不支持从 ReadableStream
或 AsyncIterator
流式传输响应体。幸运的是,你仍然可以先将响应缓冲到内存中
const time = await fetch("https://api.example.com/v1/data");
// Buffer the response in memory first.
const blob = await time.blob();
const server = Bun.serve({
static: {
"/api/data": new Response(blob),
},
fetch(req) {
return new Response("404!");
},
});
路由优先级
路由按照以下优先级顺序匹配:
- 精确路由 (
/users/all
) - 参数路由 (
/users/:id
) - 通配符路由 (
/users/*
) - 全局捕获所有路由 (
/*
)
Bun.serve({
routes: {
// Most specific first
"/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"),
},
});
每个 HTTP 方法的路由
路由处理程序可以按 HTTP 方法进行专门化
Bun.serve({
routes: {
"/api/posts": {
// Different handlers per method
GET: () => new Response("List posts"),
POST: async req => {
const post = await req.json();
return Response.json({ id: crypto.randomUUID(), ...post });
},
PUT: async req => {
const updates = await req.json();
return Response.json({ updated: true, ...updates });
},
DELETE: () => new Response(null, { status: 204 }),
},
},
});
你可以传递以下任何方法:
方法 | 用例示例 |
---|---|
GET | 获取资源 |
HEAD | 检查资源是否存在 |
OPTIONS | 获取允许的 HTTP 方法 (CORS) |
DELETE | 删除资源 |
PATCH | 更新资源 |
POST | 创建资源 |
PUT | 更新资源 |
当传递函数而不是对象时,所有方法都将由该函数处理
const server = Bun.serve({
routes: {
"/api/version": () => Response.json({ version: "1.0.0" }),
},
});
await fetch(new URL("/api/version", server.url));
await fetch(new URL("/api/version", server.url), { method: "PUT" });
// ... etc
热路由重载
使用 server.reload()
更新路由,无需重启服务器
const server = Bun.serve({
routes: {
"/api/version": () => Response.json({ version: "1.0.0" }),
},
});
// Deploy new routes without downtime
server.reload({
routes: {
"/api/version": () => Response.json({ version: "2.0.0" }),
},
});
错误处理
Bun 为路由提供结构化错误处理
Bun.serve({
routes: {
// Errors are caught automatically
"/api/risky": () => {
throw new Error("Something went wrong");
},
},
// Global error handler
error(error) {
console.error(error);
return new Response(`Internal Error: ${error.message}`, {
status: 500,
headers: {
"Content-Type": "text/plain",
},
});
},
});
HTML 导入
要添加客户端单页应用程序,你可以使用 HTML 导入
import myReactSinglePageApp from "./index.html";
Bun.serve({
routes: {
"/": myReactSinglePageApp,
},
});
HTML 导入不仅仅提供 HTML。它是一个功能齐全的前端打包器、转译器和工具包,使用 Bun 的 打包器、JavaScript 转译器和 CSS 解析器构建。
你可以使用它来构建功能齐全的前端,包括 React、TypeScript、Tailwind CSS 等。查看 /docs/bundler/fullstack 了解更多信息。
实际示例: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: {
// List posts
"/api/posts": {
GET: () => {
const posts = db.query("SELECT * FROM posts").all();
return Response.json(posts);
},
// Create post
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 });
},
},
// Get post by ID
"/api/posts/:id": req => {
const post = db
.query("SELECT * FROM posts WHERE id = ?")
.get(req.params.id);
if (!post) {
return new Response("Not Found", { status: 404 });
}
return Response.json(post);
},
},
error(error) {
console.error(error);
return new Response("Internal Server Error", { status: 500 });
},
});
export interface Post {
id: string;
title: string;
content: string;
created_at: string;
}
路由性能
Bun.serve()
的路由器构建于 uWebSocket 的 基于树的方法之上,添加了 SIMD 加速的路由参数解码 和 JavaScriptCore 结构缓存,以将现代硬件的性能发挥到极致。
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) {
// Forward the request to another server.
return fetch("https://example.com");
},
});
你还可以从 fetch
处理程序访问 Server
对象。它是传递给 fetch
函数的第二个参数。
// `server` is passed in as the second argument to `fetch`.
const server = Bun.serve({
fetch(req, server) {
const ip = server.requestIP(req);
return new Response(`Your IP is ${ip}`);
},
});
更改 port
和 hostname
要配置服务器将监听的端口和主机名,请在 options 对象中设置 port
和 hostname
。
Bun.serve({
port: 8080, // defaults to $BUN_PORT, $PORT, $NODE_PORT otherwise 3000
hostname: "mydomain.com", // defaults to "0.0.0.0"
fetch(req) {
return new Response("404!");
},
});
要随机选择一个可用端口,请将 port
设置为 0
。
const server = Bun.serve({
port: 0, // random port
fetch(req) {
return new Response("404!");
},
});
// server.port is the randomly selected port
console.log(server.port);
你可以通过访问服务器对象上的 port
属性或访问 url
属性来查看选择的端口。
console.log(server.port); // 3000
console.log(server.url); // http://localhost:3000
配置默认端口
Bun 支持多个选项和环境变量来配置默认端口。当未设置 port
选项时,将使用默认端口。
--port
命令行标志
bun --port=4002 server.ts
BUN_PORT
环境变量
BUN_PORT=4002 bun server.ts
PORT
环境变量
PORT=4002 bun server.ts
NODE_PORT
环境变量
NODE_PORT=4002 bun server.ts
Unix 域套接字
要在 unix 域套接字上监听,请传递带有套接字路径的 unix
选项。
Bun.serve({
unix: "/tmp/my-socket.sock", // path to socket
fetch(req) {
return new Response(`404!`);
},
});
抽象命名空间套接字
Bun 支持 Linux 抽象命名空间套接字。要使用抽象命名空间套接字,请在 unix
路径前加上空字节。
Bun.serve({
unix: "\0my-abstract-socket", // abstract namespace socket
fetch(req) {
return new Response(`404!`);
},
});
与 unix 域套接字不同,抽象命名空间套接字不绑定到文件系统,并且在对套接字的最后一个引用关闭时会自动删除。
错误处理
要激活开发模式,请设置 development: true
。
Bun.serve({
development: true,
fetch(req) {
throw new Error("woops!");
},
});
在开发模式下,Bun 将在浏览器中使用内置错误页面显示错误。

error
回调
要处理服务器端错误,请实现 error
处理程序。此函数应返回 Response
,以便在发生错误时提供给客户端。此响应将取代 development
模式下 Bun 的默认错误页面。
Bun.serve({
fetch(req) {
throw new Error("woops!");
},
error(error) {
return new Response(`<pre>${error}\n${error.stack}</pre>`, {
headers: {
"Content-Type": "text/html",
},
});
},
});
调用 Bun.serve
返回一个 Server
对象。要停止服务器,请调用 .stop()
方法。
const server = Bun.serve({
fetch() {
return new Response("Bun!");
},
});
server.stop();
TLS
Bun 开箱即用地支持 TLS,由 BoringSSL 提供支持。通过传入 key
和 cert
的值来启用 TLS;启用 TLS 需要同时提供这两个值。
Bun.serve({
fetch(req) {
return new Response("Hello!!!");
},
tls: {
key: Bun.file("./key.pem"),
cert: Bun.file("./cert.pem"),
}
});
key
和 cert
字段期望你的 TLS 密钥和证书的内容,而不是其路径。这可以是字符串、BunFile
、TypedArray
或 Buffer
。
Bun.serve({
fetch() {},
tls: {
// BunFile
key: Bun.file("./key.pem"),
// Buffer
key: fs.readFileSync("./key.pem"),
// string
key: fs.readFileSync("./key.pem", "utf8"),
// array of above
key: [Bun.file("./key1.pem"), Bun.file("./key2.pem")],
},
});
如果你的私钥使用密码加密,请提供 passphrase
的值以解密它。
Bun.serve({
fetch(req) {
return new Response("Hello!!!");
},
tls: {
key: Bun.file("./key.pem"),
cert: Bun.file("./cert.pem"),
passphrase: "my-secret-passphrase",
}
});
可选地,你可以通过传递 ca
的值来覆盖受信任的 CA 证书。默认情况下,服务器将信任 Mozilla 策划的知名 CA 列表。当指定 ca
时,Mozilla 列表将被覆盖。
Bun.serve({
fetch(req) {
return new Response("Hello!!!");
},
tls: {
key: Bun.file("./key.pem"), // path to TLS key
cert: Bun.file("./cert.pem"), // path to TLS cert
ca: Bun.file("./ca.pem"), // path to root CA certificate
}
});
要覆盖 Diffie-Hellman 参数
Bun.serve({
// ...
tls: {
// other config
dhParamsFile: "/path/to/dhparams.pem", // path to Diffie Hellman parameters
},
});
服务器名称指示 (SNI)
要配置服务器的服务器名称指示 (SNI),请在 tls
对象中设置 serverName
字段。
Bun.serve({
// ...
tls: {
// ... other config
serverName: "my-server.com", // SNI
},
});
要允许多个服务器名称,请将对象数组传递给 tls
,每个对象都包含一个 serverName
字段。
Bun.serve({
// ...
tls: [
{
key: Bun.file("./key1.pem"),
cert: Bun.file("./cert1.pem"),
serverName: "my-server1.com",
},
{
key: Bun.file("./key2.pem"),
cert: Bun.file("./cert2.pem"),
serverName: "my-server2.com",
},
],
});
idleTimeout
要配置空闲超时,请在 Bun.serve 中设置 idleTimeout
字段。
Bun.serve({
// 10 seconds:
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 看到一个包含 default
导出并且其中包含 fetch
处理程序的文件时,它会在底层将其传递到 Bun.serve
中。
流式传输文件
要流式传输文件,请返回一个以 BunFile
对象作为主体的 Response
对象。
Bun.serve({
fetch(req) {
return new Response(Bun.file("./hello.txt"));
},
});
⚡️ 速度 — Bun 在可能的情况下自动使用 sendfile(2)
系统调用,从而在内核中实现零拷贝文件传输——这是发送文件的最快方式。
你可以使用 Bun.file
对象上的 slice(start, end)
方法发送文件的一部分。这会自动在 Response
对象上设置 Content-Range
和 Content-Length
标头。
Bun.serve({
fetch(req) {
// parse `Range` header
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]
// return a slice of the file
const bigFile = Bun.file("./big-video.mp4");
return new Response(bigFile.slice(start, end));
},
});
服务器生命周期方法
server.stop() - 停止服务器
要停止服务器接受新连接
const server = Bun.serve({
fetch(req) {
return new Response("Hello!");
},
});
// Gracefully stop the server (waits for in-flight requests)
await server.stop();
// Force stop and close all active connections
await server.stop(true);
默认情况下,stop()
允许正在处理的请求和 WebSocket 连接完成。传递 true
以立即终止所有连接。
server.ref() 和 server.unref() - 进程生命周期控制
控制服务器是否保持 Bun 进程存活
// Don't keep process alive if server is the only thing running
server.unref();
// Restore default behavior - keep process alive
server.ref();
server.reload() - 热重载处理程序
更新服务器的处理程序,无需重启
const server = Bun.serve({
routes: {
"/api/version": Response.json({ version: "v1" }),
},
fetch(req) {
return new Response("v1");
},
});
// Update to new handler
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({
fetch(req, server) {
// Set 60 second timeout for this request
server.timeout(req, 60);
// If they take longer than 60 seconds to send the body, the request will be aborted
await req.text();
return new Response("Done!");
},
});
传递 0
以禁用请求的超时。
server.requestIP(Request) - 获取客户端信息
获取客户端 IP 和端口信息
const server = Bun.serve({
fetch(req, server) {
const address = server.requestIP(req);
if (address) {
return new Response(
`Client IP: ${address.address}, Port: ${address.port}`,
);
}
return new Response("Unknown client");
},
});
对于已关闭的请求或 Unix 域套接字,返回 null
。
服务器指标
server.pendingRequests 和 server.pendingWebSockets
使用内置计数器监控服务器活动
const server = Bun.serve({
fetch(req, server) {
return new Response(
`Active requests: ${server.pendingRequests}\n` +
`Active WebSockets: ${server.pendingWebSockets}`,
);
},
});
server.subscriberCount(topic) - WebSocket 订阅者
获取 WebSocket 主题的订阅者数量
const server = Bun.serve({
fetch(req, server) {
const chatUsers = server.subscriberCount("chat");
return new Response(`${chatUsers} users in chat`);
},
websocket: {
message(ws) {
ws.subscribe("chat");
},
},
});
WebSocket 配置
server.publish(topic, data, compress) - WebSocket 消息发布
服务器可以向所有订阅了某个 WebSocket 主题的客户端发布消息
const server = Bun.serve({
websocket: {
message(ws) {
// Publish to all "chat" subscribers
server.publish("chat", "Hello everyone!");
},
},
fetch(req) {
// ...
},
});
publish()
方法返回
- 如果成功,则返回已发送的字节数
- 如果消息被丢弃,则返回
0
- 如果应用了背压,则返回
-1
WebSocket 处理程序选项
配置 WebSocket 时,可以通过 websocket
处理程序使用几个高级选项
Bun.serve({
websocket: {
// Maximum message size (in bytes)
maxPayloadLength: 64 * 1024,
// Backpressure limit before messages are dropped
backpressureLimit: 1024 * 1024,
// Close connection if backpressure limit is hit
closeOnBackpressureLimit: true,
// Handler called when backpressure is relieved
drain(ws) {
console.log("Backpressure relieved");
},
// Enable per-message deflate compression
perMessageDeflate: {
compress: true,
decompress: true,
},
// Send ping frames to keep connection alive
sendPings: true,
// Handlers for ping/pong frames
ping(ws, data) {
console.log("Received ping");
},
pong(ws, data) {
console.log("Received pong");
},
// Whether server receives its own published messages
publishToSelf: false,
},
});
基准测试
以下是 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);
在 Linux 上,Bun.serve
服务器每秒可以处理大约 2.5 倍于 Node.js 的请求。
运行时 | 每秒请求数 |
---|---|
Node 16 | ~64,000 |
Bun | ~160,000 |

参考
请参阅 TypeScript 定义