Bun HTTP 服务 #

概述 #

Bun 内置了高性能的 HTTP 服务器 Bun.serve,无需安装 Express 或 Fastify 等框架即可快速创建 Web 服务。

基础用法 #

最简服务器 #

typescript
Bun.serve({
  port: 3000,
  fetch(request) {
    return new Response("Hello, Bun!");
  },
});

console.log("Server running at http://localhost:3000");

返回 JSON #

typescript
Bun.serve({
  port: 3000,
  fetch(request) {
    return Response.json({
      message: "Hello, Bun!",
      timestamp: Date.now(),
    });
  },
});

返回 HTML #

typescript
Bun.serve({
  port: 3000,
  fetch(request) {
    return new Response(
      `<!DOCTYPE html>
      <html>
        <head><title>Bun Server</title></head>
        <body><h1>Hello, Bun!</h1></body>
      </html>`,
      {
        headers: { "Content-Type": "text/html" },
      }
    );
  },
});

请求处理 #

获取请求信息 #

typescript
Bun.serve({
  port: 3000,
  fetch(request) {
    const url = new URL(request.url);
    
    const info = {
      method: request.method,
      url: request.url,
      pathname: url.pathname,
      search: url.search,
      headers: Object.fromEntries(request.headers),
    };
    
    return Response.json(info);
  },
});

解析请求体 #

typescript
Bun.serve({
  port: 300,
  async fetch(request) {
    const url = new URL(request.url);
    
    if (url.pathname === "/json" && request.method === "POST") {
      const body = await request.json();
      return Response.json({ received: body });
    }
    
    if (url.pathname === "/text" && request.method === "POST") {
      const text = await request.text();
      return new Response(`Received: ${text}`);
    }
    
    if (url.pathname === "/form" && request.method === "POST") {
      const formData = await request.formData();
      const name = formData.get("name");
      return Response.json({ name });
    }
    
    return new Response("Not Found", { status: 404 });
  },
});

解析查询参数 #

typescript
Bun.serve({
  port: 3000,
  fetch(request) {
    const url = new URL(request.url);
    const params = url.searchParams;
    
    const page = params.get("page") || "1";
    const limit = params.get("limit") || "10";
    const sort = params.get("sort") || "created";
    
    return Response.json({
      page: parseInt(page),
      limit: parseInt(limit),
      sort,
    });
  },
});

路由 #

基础路由 #

typescript
const routes: Record<string, (req: Request) => Response | Promise<Response>> = {
  "/": () => new Response("Home"),
  "/about": () => new Response("About"),
  "/api/status": () => Response.json({ status: "ok" }),
};

Bun.serve({
  port: 3000,
  fetch(request) {
    const url = new URL(request.url);
    const handler = routes[url.pathname];
    
    if (handler) {
      return handler(request);
    }
    
    return new Response("Not Found", { status: 404 });
  },
});

RESTful 路由 #

typescript
interface User {
  id: string;
  name: string;
  email: string;
}

const users: User[] = [
  { id: "1", name: "Alice", email: "alice@example.com" },
  { id: "2", name: "Bob", email: "bob@example.com" },
];

Bun.serve({
  port: 3000,
  async fetch(request) {
    const url = new URL(request.url);
    const method = request.method;
    
    // GET /users
    if (url.pathname === "/users" && method === "GET") {
      return Response.json(users);
    }
    
    // GET /users/:id
    const userMatch = url.pathname.match(/^\/users\/(\d+)$/);
    if (userMatch && method === "GET") {
      const user = users.find((u) => u.id === userMatch[1]);
      if (user) {
        return Response.json(user);
      }
      return new Response("User not found", { status: 404 });
    }
    
    // POST /users
    if (url.pathname === "/users" && method === "POST") {
      const body = (await request.json()) as Partial<User>;
      const newUser: User = {
        id: String(users.length + 1),
        name: body.name || "",
        email: body.email || "",
      };
      users.push(newUser);
      return Response.json(newUser, { status: 201 });
    }
    
    // PUT /users/:id
    const putMatch = url.pathname.match(/^\/users\/(\d+)$/);
    if (putMatch && method === "PUT") {
      const index = users.findIndex((u) => u.id === putMatch[1]);
      if (index === -1) {
        return new Response("User not found", { status: 404 });
      }
      const body = (await request.json()) as Partial<User>;
      users[index] = { ...users[index], ...body };
      return Response.json(users[index]);
    }
    
    // DELETE /users/:id
    const deleteMatch = url.pathname.match(/^\/users\/(\d+)$/);
    if (deleteMatch && method === "DELETE") {
      const index = users.findIndex((u) => u.id === deleteMatch[1]);
      if (index === -1) {
        return new Response("User not found", { status: 404 });
      }
      users.splice(index, 1);
      return new Response(null, { status: 204 });
    }
    
    return new Response("Not Found", { status: 404 });
  },
});

路由器类 #

typescript
class Router {
  private routes: Array<{
    method: string;
    pattern: RegExp;
    handler: (req: Request, params: Record<string, string>) => Response | Promise<Response>;
  }> = [];

  get(pattern: string, handler: (req: Request, params: Record<string, string>) => Response | Promise<Response>) {
    this.routes.push({ method: "GET", pattern: this.toRegExp(pattern), handler });
  }

  post(pattern: string, handler: (req: Request, params: Record<string, string>) => Response | Promise<Response>) {
    this.routes.push({ method: "POST", pattern: this.toRegExp(pattern), handler });
  }

  put(pattern: string, handler: (req: Request, params: Record<string, string>) => Response | Promise<Response>) {
    this.routes.push({ method: "PUT", pattern: this.toRegExp(pattern), handler });
  }

  delete(pattern: string, handler: (req: Request, params: Record<string, string>) => Response | Promise<Response>) {
    this.routes.push({ method: "DELETE", pattern: this.toRegExp(pattern), handler });
  }

  private toRegExp(pattern: string): RegExp {
    const paramNames: string[] = [];
    const regexPattern = pattern.replace(/:([^/]+)/g, (_, name) => {
      paramNames.push(name);
      return "([^/]+)";
    });
    return new RegExp(`^${regexPattern}$`);
  }

  async handle(request: Request): Promise<Response> {
    const url = new URL(request.url);
    const pathname = url.pathname;

    for (const route of this.routes) {
      if (route.method !== request.method) continue;
      
      const match = pathname.match(route.pattern);
      if (match) {
        const params = match.slice(1).reduce((acc, value, index) => {
          acc[index.toString()] = value;
          return acc;
        }, {} as Record<string, string>);
        
        return route.handler(request, params);
      }
    }

    return new Response("Not Found", { status: 404 });
  }
}

// 使用
const router = new Router();

router.get("/", () => new Response("Home"));
router.get("/users/:id", (req, params) => Response.json({ id: params["0"] }));
router.post("/users", async (req) => {
  const body = await req.json();
  return Response.json(body, { status: 201 });
});

Bun.serve({
  port: 3000,
  fetch: (req) => router.handle(req),
});

中间件 #

基础中间件 #

typescript
type Middleware = (
  request: Request,
  next: () => Promise<Response>
) => Promise<Response> | Response;

function createServer() {
  const middlewares: Middleware[] = [];

  function use(middleware: Middleware) {
    middlewares.push(middleware);
  }

  async function handle(request: Request): Promise<Response> {
    let index = 0;

    async function next(): Promise<Response> {
      if (index < middlewares.length) {
        const middleware = middlewares[index++];
        return middleware(request, next);
      }
      return new Response("Not Found", { status: 404 });
    }

    return next();
  }

  return { use, handle };
}

// 使用
const server = createServer();

// 日志中间件
server.use(async (request, next) => {
  const start = Date.now();
  const response = await next();
  const duration = Date.now() - start;
  console.log(`${request.method} ${request.url} - ${response.status} (${duration}ms)`);
  return response;
});

// CORS 中间件
server.use(async (request, next) => {
  if (request.method === "OPTIONS") {
    return new Response(null, {
      headers: {
        "Access-Control-Allow-Origin": "*",
        "Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
        "Access-Control-Allow-Headers": "Content-Type, Authorization",
      },
    });
  }
  
  const response = await next();
  response.headers.set("Access-Control-Allow-Origin", "*");
  return response;
});

// 认证中间件
server.use(async (request, next) => {
  const url = new URL(request.url);
  if (url.pathname.startsWith("/api/protected")) {
    const auth = request.headers.get("Authorization");
    if (!auth || !auth.startsWith("Bearer ")) {
      return new Response("Unauthorized", { status: 401 });
    }
  }
  return next();
});

Bun.serve({
  port: 3000,
  fetch: server.handle,
});

内置中间件集合 #

typescript
// 日志中间件
function logger() {
  return async (request: Request, next: () => Promise<Response>) => {
    const start = Date.now();
    const response = await next();
    console.log(
      `[${new Date().toISOString()}] ${request.method} ${new URL(request.url).pathname} - ${response.status} (${Date.now() - start}ms)`
    );
    return response;
  };
}

// CORS 中间件
function cors(options: {
  origin?: string;
  methods?: string[];
  headers?: string[];
} = {}) {
  const { origin = "*", methods = ["GET", "POST", "PUT", "DELETE"], headers = ["Content-Type"] } = options;

  return async (request: Request, next: () => Promise<Response>) => {
    if (request.method === "OPTIONS") {
      return new Response(null, {
        headers: {
          "Access-Control-Allow-Origin": origin,
          "Access-Control-Allow-Methods": methods.join(", "),
          "Access-Control-Allow-Headers": headers.join(", "),
        },
      });
    }

    const response = await next();
    response.headers.set("Access-Control-Allow-Origin", origin);
    return response;
  };
}

// JSON 解析中间件
function jsonParser() {
  return async (request: Request, next: () => Promise<Response>) => {
    if (request.headers.get("Content-Type")?.includes("application/json")) {
      try {
        const body = await request.json();
        (request as any).body = body;
      } catch (e) {
        return new Response("Invalid JSON", { status: 400 });
      }
    }
    return next();
  };
}

// 错误处理中间件
function errorHandler() {
  return async (request: Request, next: () => Promise<Response>) => {
    try {
      return await next();
    } catch (error) {
      console.error("Error:", error);
      return Response.json(
        { error: "Internal Server Error", message: String(error) },
        { status: 500 }
      );
    }
  };
}

静态文件服务 #

基础静态服务 #

typescript
Bun.serve({
  port: 3000,
  async fetch(request) {
    const url = new URL(request.url);
    let pathname = url.pathname;
    
    if (pathname === "/") {
      pathname = "/index.html";
    }
    
    const filePath = `./public${pathname}`;
    const file = Bun.file(filePath);
    
    if (await file.exists()) {
      return new Response(file);
    }
    
    return new Response("Not Found", { status: 404 });
  },
});

高级静态服务 #

typescript
const MIME_TYPES: Record<string, string> = {
  ".html": "text/html",
  ".css": "text/css",
  ".js": "application/javascript",
  ".json": "application/json",
  ".png": "image/png",
  ".jpg": "image/jpeg",
  ".gif": "image/gif",
  ".svg": "image/svg+xml",
  ".ico": "image/x-icon",
  ".woff": "font/woff",
  ".woff2": "font/woff2",
  ".ttf": "font/ttf",
};

Bun.serve({
  port: 3000,
  async fetch(request) {
    const url = new URL(request.url);
    let pathname = url.pathname;
    
    if (pathname === "/") {
      pathname = "/index.html";
    }
    
    const filePath = `./public${pathname}`;
    const file = Bun.file(filePath);
    
    if (!(await file.exists())) {
      // SPA 回退
      const indexFile = Bun.file("./public/index.html");
      if (await indexFile.exists()) {
        return new Response(indexFile, {
          headers: { "Content-Type": "text/html" },
        });
      }
      return new Response("Not Found", { status: 404 });
    }
    
    const ext = pathname.substring(pathname.lastIndexOf("."));
    const contentType = MIME_TYPES[ext] || "application/octet-stream";
    
    return new Response(file, {
      headers: {
        "Content-Type": contentType,
        "Cache-Control": "public, max-age=31536000",
      },
    });
  },
});

WebSocket #

WebSocket 服务器 #

typescript
Bun.serve({
  port: 3000,
  fetch(request, server) {
    const url = new URL(request.url);
    
    if (url.pathname === "/ws") {
      const success = server.upgrade(request);
      if (success) {
        return undefined;
      }
      return new Response("WebSocket upgrade failed", { status: 400 });
    }
    
    return new Response("Hello HTTP!");
  },
  websocket: {
    open(ws) {
      console.log("WebSocket opened");
      ws.subscribe("chat");
    },
    message(ws, message) {
      console.log("Received:", message);
      ws.publish("chat", message);
    },
    close(ws) {
      console.log("WebSocket closed");
      ws.unsubscribe("chat");
    },
  },
});

console.log("Server running at http://localhost:3000");

广播消息 #

typescript
const clients = new Set<WebSocket>();

Bun.serve({
  port: 3000,
  fetch(request, server) {
    if (server.upgrade(request)) {
      return undefined;
    }
    return new Response("Hello");
  },
  websocket: {
    open(ws) {
      clients.add(ws as any);
      console.log(`Client connected. Total: ${clients.size}`);
    },
    message(ws, message) {
      for (const client of clients) {
        if (client !== ws) {
          client.send(message);
        }
      }
    },
    close(ws) {
      clients.delete(ws as any);
      console.log(`Client disconnected. Total: ${clients.size}`);
    },
  },
});

聊天室示例 #

typescript
interface ChatMessage {
  type: "join" | "leave" | "message";
  user: string;
  content?: string;
  timestamp: number;
}

const rooms = new Map<string, Set<WebSocket>>();

Bun.serve({
  port: 3000,
  fetch(request, server) {
    const url = new URL(request.url);
    
    if (url.pathname.startsWith("/chat/")) {
      const roomId = url.pathname.replace("/chat/", "");
      const success = server.upgrade(request, { data: { roomId } });
      if (success) return undefined;
    }
    
    return new Response("Not Found", { status: 404 });
  },
  websocket: {
    open(ws) {
      const { roomId } = ws.data as { roomId: string };
      
      if (!rooms.has(roomId)) {
        rooms.set(roomId, new Set());
      }
      rooms.get(roomId)!.add(ws as any);
      
      ws.subscribe(roomId);
      ws.publish(roomId, JSON.stringify({
        type: "join",
        user: "Anonymous",
        timestamp: Date.now(),
      } as ChatMessage));
    },
    message(ws, message) {
      const { roomId } = ws.data as { roomId: string };
      const data = JSON.parse(message.toString());
      
      ws.publish(roomId, JSON.stringify({
        type: "message",
        user: data.user,
        content: data.content,
        timestamp: Date.now(),
      } as ChatMessage));
    },
    close(ws) {
      const { roomId } = ws.data as { roomId: string };
      rooms.get(roomId)?.delete(ws as any);
      ws.unsubscribe(roomId);
    },
  },
});

SSL/TLS #

HTTPS 服务器 #

typescript
Bun.serve({
  port: 443,
  tls: {
    cert: Bun.file("./cert.pem"),
    key: Bun.file("./key.pem"),
  },
  fetch(request) {
    return new Response("Hello, HTTPS!");
  },
});

HTTP/2 #

typescript
Bun.serve({
  port: 443,
  tls: {
    cert: Bun.file("./cert.pem"),
    key: Bun.file("./key.pem"),
  },
  fetch(request) {
    return new Response("Hello, HTTP/2!");
  },
});

性能优化 #

连接配置 #

typescript
Bun.serve({
  port: 3000,
  fetch(request) {
    return new Response("Hello!");
  },
  // 最大请求体大小
  maxRequestBodySize: 1024 * 1024 * 10, // 10MB
  // 开发模式
  development: true,
  // 错误处理
  error(error) {
    return new Response(`Error: ${error.message}`, { status: 500 });
  },
});

响应缓存 #

typescript
const cache = new Map<string, { response: Response; expires: number }>();

Bun.serve({
  port: 3000,
  async fetch(request) {
    const url = request.url;
    const cached = cache.get(url);
    
    if (cached && cached.expires > Date.now()) {
      return cached.response;
    }
    
    const response = await generateResponse(request);
    cache.set(url, {
      response,
      expires: Date.now() + 60000, // 1 分钟
    });
    
    return response;
  },
});

下一步 #

现在你已经了解了 Bun 的 HTTP 服务,接下来学习 文件操作 深入了解 Bun 的文件系统 API。

最后更新:2026-03-29