> ## Documentation Index
> Fetch the complete documentation index at: https://bun.zhcndoc.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 全栈开发服务器

> 使用 Bun 集成的开发服务器构建全栈应用，它能够打包前端资源并处理 API 路由

首先，导入 HTML 文件并将它们传递给 `Bun.serve()` 的 `routes` 选项。

```ts title="app.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { serve } from "bun";
import dashboard from "./dashboard.html";
import homepage from "./index.html";

const server = serve({
  routes: {
    // ** HTML 导入 **
    // 将 index.html 打包并路由到 "/"
    // 这使用 HTMLRewriter 扫描 HTML 中的 `<script>` 和 `<link>` 标签，
    // 对它们运行 Bun 的 JavaScript 和 CSS 打包器，
    // 转译任何 TypeScript、JSX 和 TSX，
    // 使用 Bun 的 CSS 解析器降低 CSS 级别并提供结果。
    "/": homepage,
    // 将 dashboard.html 打包并路由到 "/dashboard"
    "/dashboard": dashboard,

    // ** API 端点 **（需要 Bun v1.2.3+）
    "/api/users": {
      async GET(req) {
        const users = await sql`SELECT * FROM users`;
        return Response.json(users);
      },
      async POST(req) {
        const { name, email } = await req.json();
        const [user] = await sql`INSERT INTO users (name, email) VALUES (${name}, ${email})`;
        return Response.json(user);
      },
    },
    "/api/users/:id": async req => {
      const { id } = req.params;
      const [user] = await sql`SELECT * FROM users WHERE id = ${id}`;
      return Response.json(user);
    },
  },

  // 启用开发模式以获得：
  // - 详细的错误信息
  // - 热重载（需要 Bun v1.2.3+）
  development: true,
});

console.log(`Listening on ${server.url}`);
```

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun run app.ts
```

## HTML 路由

### 将 HTML 导入作为路由

Web 以 HTML 开始，Bun 的全栈开发服务器也是如此。

要指定前端入口点，请将 HTML 文件导入到你的 JavaScript/TypeScript/TSX/JSX 文件中。

```ts title="app.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import dashboard from "./dashboard.html";
import homepage from "./index.html";
```

这些 HTML 文件被用作 Bun 开发服务器中的路由，传递给 `Bun.serve()`。

```ts title="app.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
Bun.serve({
  routes: {
    "/": homepage,
    "/dashboard": dashboard,
  },

  fetch(req) {
    // ... API 请求
  },
});
```

当你请求 `/dashboard` 或 `/` 时，Bun 会自动打包 HTML 文件中的 `<script>` 和 `<link>` 标签，将它们暴露为静态路由并提供结果。

### HTML 处理示例

一个 `index.html` 文件如下：

```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!DOCTYPE html>
<html>
  <head>
    <title>Home</title>
    <link rel="stylesheet" href="./reset.css" />
    <link rel="stylesheet" href="./styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="./sentry-and-preloads.ts"></script>
    <script type="module" src="./my-app.tsx"></script>
  </body>
</html>
```

经过处理后可能成为：

```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!DOCTYPE html>
<html>
  <head>
    <title>Home</title>
    <link rel="stylesheet" href="/index-[hash].css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/index-[hash].js"></script>
  </body>
</html>
```

## React 集成

要在客户端代码中使用 React，导入 `react-dom/client` 并渲染你的应用。

<CodeGroup>
  ```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import dashboard from "../public/dashboard.html";
  import { serve } from "bun";

  serve({
    routes: {
      "/": dashboard,
    },
    async fetch(req) {
      // ...api 请求
      return new Response("hello world");
    },
  });
  ```

  ```tsx title="src/frontend.tsx" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { createRoot } from "react-dom/client";
  import App from "./app";

  const container = document.getElementById("root");
  const root = createRoot(container!);
  root.render(<App />);
  ```

  ```html title="public/dashboard.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
  <!DOCTYPE html>
  <html>
    <head>
      <title>Dashboard</title>
      <link rel="stylesheet" href="../src/styles.css" />
    </head>
    <body>
      <div id="root"></div>
      <script type="module" src="../src/frontend.tsx"></script>
    </body>
  </html>
  ```

  ```tsx title="src/app.tsx" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
  import { useState } from "react";

  export default function App() {
    const [count, setCount] = useState(0);

    return (
      <div>
        <h1>Dashboard</h1>
        <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      </div>
    );
  }
  ```
</CodeGroup>

## 开发模式

本地开发时，通过在 `Bun.serve()` 中设置 `development: true` 启用开发模式。

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import homepage from "./index.html";
import dashboard from "./dashboard.html";

Bun.serve({
  routes: {
    "/": homepage,
    "/dashboard": dashboard,
  },

  development: true,

  fetch(req) {
    // ... API 请求
  },
});
```

### 开发模式功能

当 `development` 为 `true` 时，Bun 会：

* 在响应中包含 SourceMap 头，方便调试工具显示原始源码
* 禁用代码压缩
* 每次请求 `.html` 文件时重新打包资源
* 启用热模块重载（除非设置 `hmr: false`）
* 将浏览器中的控制台日志回显到终端

### 高级开发配置

`Bun.serve()` 支持将浏览器中的控制台日志回显到终端。

要启用此功能，在 `Bun.serve()` 的开发模式对象中传递 `console: true`。

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import homepage from "./index.html";

Bun.serve({
  // development 也可以是一个对象
  development: {
    // 启用热模块重载
    hmr: true,

    // 将浏览器中的控制台日志回显到终端
    console: true,
  },

  routes: {
    "/": homepage,
  },
});
```

设置 `console: true` 后，Bun 会通过热模块重载的 WebSocket 连接将浏览器的日志流式传输到终端。

### 开发 vs 生产

| 功能              | 开发环境         | 生产环境  |
| --------------- | ------------ | ----- |
| **Source maps** | ✅ 启用         | ❌ 禁用  |
| **代码压缩**        | ❌ 禁用         | ✅ 启用  |
| **热重载**         | ✅ 启用         | ❌ 禁用  |
| **资源打包**        | 🔄 每次请求      | 💾 缓存 |
| **控制台日志**       | 🖥️ 浏览器 → 终端 | ❌ 禁用  |
| **错误详情**        | 📝 详细        | 🔒 简洁 |

## 生产模式

热重载和 `development: true` 有助于快速迭代，但生产环境下你的服务器应尽可能快速且依赖尽可能少。

### 预构建打包（推荐）

从 Bun v1.2.17 开始，你可以使用 `Bun.build` 或 `bun build` 预先打包全栈应用。

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun build --target=bun --production --outdir=dist ./src/index.ts
```

当 Bun 的打包器检测到服务器端代码中导入 HTML 文件时，会将引用的 JS/TS/TSX/JSX 和 CSS 文件打包成一个 `Bun.serve()` 可用的清单对象。

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { serve } from "bun";
import index from "./index.html";

serve({
  routes: { "/": index },
});
```

### 运行时打包

当构建步骤太复杂时，你可以在 `Bun.serve()` 设置 `development: false`。

这将：

* 启用内存中缓存打包资源。首次请求 `.html` 文件时 Bun 自行打包，并缓存结果直到服务器重启。
* 启用 `Cache-Control` 以及 `ETag` 头
* 压缩 JavaScript/TypeScript/TSX/JSX 文件

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { serve } from "bun";
import homepage from "./index.html";

serve({
  routes: {
    "/": homepage,
  },

  // 生产模式
  development: false,
});
```

## API 路由

### HTTP 方法处理器

通过 HTTP 方法处理器定义 API 端点：

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { serve } from "bun";

serve({
  routes: {
    "/api/users": {
      async GET(req) {
        // 处理 GET 请求
        const users = await getUsers();
        return Response.json(users);
      },

      async POST(req) {
        // 处理 POST 请求
        const userData = await req.json();
        const user = await createUser(userData);
        return Response.json(user, { status: 201 });
      },

      async PUT(req) {
        // 处理 PUT 请求
        const userData = await req.json();
        const user = await updateUser(userData);
        return Response.json(user);
      },

      async DELETE(req) {
        // 处理 DELETE 请求
        await deleteUser(req.params.id);
        return new Response(null, { status: 204 });
      },
    },
  },
});
```

### 动态路由

路由中使用 URL 参数：

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
serve({
  routes: {
    // 单参数
    "/api/users/:id": async req => {
      const { id } = req.params;
      const user = await getUserById(id);
      return Response.json(user);
    },

    // 多参数
    "/api/users/:userId/posts/:postId": async req => {
      const { userId, postId } = req.params;
      const post = await getPostByUser(userId, postId);
      return Response.json(post);
    },

    // 通配符路由
    "/api/files/*": async req => {
      const filePath = req.params["*"];
      const file = await getFile(filePath);
      return new Response(file);
    },
  },
});
```

### 请求处理

```ts title="src/backend.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
serve({
  routes: {
    "/api/data": {
      async POST(req) {
        // 解析 JSON 请求体
        const body = await req.json();

        // 访问请求头
        const auth = req.headers.get("Authorization");

        // 访问 URL 参数
        const { id } = req.params;

        // 访问查询参数
        const url = new URL(req.url);
        const page = url.searchParams.get("page") || "1";

        // 返回响应
        return Response.json({
          message: "Data processed",
          page: parseInt(page),
          authenticated: !!auth,
        });
      },
    },
  },
});
```

## 插件

Bun 的打包器插件在打包静态路由时同样支持。

要为 `Bun.serve` 配置插件，在 `bunfig.toml` 的 `[serve.static]` 部分添加 `plugins` 数组。

### TailwindCSS 插件

你可以通过安装 `tailwindcss` 包和 `bun-plugin-tailwind` 插件使用 TailwindCSS。

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
bun add tailwindcss bun-plugin-tailwind
```

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[serve.static]
plugins = ["bun-plugin-tailwind"]
```

这将允许你在 HTML 和 CSS 文件中使用 TailwindCSS 实用程序类。\
在项目中的某处导入 `tailwindcss`：

```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!doctype html>
<html>
  <head>
    <!-- [!code ++] -->
    <link rel="stylesheet" href="tailwindcss" />
  </head>
  <!-- 其余 HTML... -->
</html>
```

或者在 CSS 文件中导入 TailwindCSS：

```css title="style.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
@import "tailwindcss";

.custom-class {
  @apply bg-red-500 text-white;
}
```

```html index.html icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!doctype html>
<html>
  <head>
    <!-- [!code ++] -->
    <link rel="stylesheet" href="./style.css" />
  </head>
  <!-- 其余 HTML... -->
</html>
```

### 自定义插件

任何导出有效打包器插件对象（基本包含 `name` 和 `setup` 字段）的 JS 文件或模块，都可以放入插件数组：

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[serve.static]
plugins = ["./my-plugin-implementation.ts"]
```

```ts title="my-plugin-implementation.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import type { BunPlugin } from "bun";

const myPlugin: BunPlugin = {
  name: "my-custom-plugin",
  setup(build) {
    // 插件实现
    build.onLoad({ filter: /\.custom$/ }, async args => {
      const text = await Bun.file(args.path).text();
      return {
        contents: `export default ${JSON.stringify(text)};`,
        loader: "js",
      };
    });
  },
};

export default myPlugin;
```

Bun 会延迟解析和加载每个插件，并使用它们打包你的路由。

<Note>
  这当前写在 `bunfig.toml` 中，是为了在未来整合 `bun build` CLI 时能静态获知所用插件。这些插件可在 `Bun.build()` JS API
  下工作，但 CLI 尚未支持。
</Note>

## 内联环境变量

Bun 可以在构建时将前端 JavaScript 和 TypeScript 中的 `process.env.*` 替换为实际值。在 `bunfig.toml` 中配置 `env` 选项：

```toml title="bunfig.toml" icon="settings" theme={"theme":{"light":"github-light","dark":"dracula"}}
[serve.static]
env = "PUBLIC_*"  # 仅内联以 PUBLIC_ 开头的环境变量（推荐）
# env = "inline"  # 内联所有环境变量
# env = "disable" # 禁用环境变量替换（默认）
```

<Note>
  仅适用于字面量 `process.env.FOO` 引用，不支持 `import.meta.env` 或间接访问如 `const env = process.env; env.FOO`。

  如果环境变量未设置，浏览器可能会报错，如 `ReferenceError: process is not defined`。
</Note>

详情及示例请参见 [HTML & 静态站点文档](/bundler/html-static#inline-environment-variables)。

## 工作原理

Bun 使用 `HTMLRewriter` 扫描 HTML 文件中的 `<script>` 和 `<link>` 标签，作为 Bun 打包器入口点，生成针对 JS/TS/TSX/JSX 及 CSS 的优化包，并提供结果。

### 处理流程

<Steps>
  <Step title="1. <script> 处理">
    * 转译 `<script>` 标签中的 TypeScript、JSX 和 TSX
    * 打包导入的依赖
    * 生成用于调试的 sourcemap
    * 在 `Bun.serve()` 中 `development` 非 `true` 时进行代码压缩

    ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
    <script type="module" src="./counter.tsx"></script>
    ```
  </Step>

  <Step title="2. <link> 处理">
    * 处理 CSS 导入及 `<link>` 标签
    * 合并 CSS 文件
    * 重写 url 及资源路径，在 URL 中加入内容寻址哈希

    ```html title="index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
    <link rel="stylesheet" href="./styles.css" />
    ```
  </Step>

  <Step title="3. <img> 与资源处理">
    * 资源链接被重写以包含内容寻址哈希
    * CSS 文件中的小资源被内联为 `data:` URL，减少 HTTP 请求数量
  </Step>

  <Step title="4. HTML 重写">
    * 合并所有 `<script>` 标签为一个带内容寻址哈希的 `<script>` 标签
    * 合并所有 `<link>` 标签为一个带内容寻址哈希的 `<link>` 标签
    * 输出新的 HTML 文件
  </Step>

  <Step title="5. 提供服务">
    * 所有打包输出的文件都作为静态路由暴露，内部使用与传递 Response 对象给 `Bun.serve()` 中 `static` 相同机制
    * 行为类似于 `Bun.build` 对 HTML 文件的处理方式
  </Step>
</Steps>

## 完整示例

这是一个完整的全栈应用示例：

```ts title="server.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { serve } from "bun";
import { Database } from "bun:sqlite";
import homepage from "./public/index.html";
import dashboard from "./public/dashboard.html";

// 初始化数据库
const db = new Database("app.db");
db.exec(`
  CREATE TABLE IF NOT EXISTS users (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    email TEXT UNIQUE NOT NULL,
    created_at DATETIME DEFAULT CURRENT_TIMESTAMP
  )
`);

const server = serve({
  routes: {
    // 前端路由
    "/": homepage,
    "/dashboard": dashboard,

    // API 路由
    "/api/users": {
      async GET() {
        const users = db.query("SELECT * FROM users").all();
        return Response.json(users);
      },

      async POST(req) {
        const { name, email } = await req.json();

        try {
          const result = db.query("INSERT INTO users (name, email) VALUES (?, ?) RETURNING *").get(name, email);

          return Response.json(result, { status: 201 });
        } catch (error) {
          return Response.json({ error: "Email already exists" }, { status: 400 });
        }
      },
    },

    "/api/users/:id": {
      async GET(req) {
        const { id } = req.params;
        const user = db.query("SELECT * FROM users WHERE id = ?").get(id);

        if (!user) {
          return Response.json({ error: "User not found" }, { status: 404 });
        }

        return Response.json(user);
      },

      async DELETE(req) {
        const { id } = req.params;
        const result = db.query("DELETE FROM users WHERE id = ?").run(id);

        if (result.changes === 0) {
          return Response.json({ error: "User not found" }, { status: 404 });
        }

        return new Response(null, { status: 204 });
      },
    },

    // 健康检查端点
    "/api/health": {
      GET() {
        return Response.json({
          status: "ok",
          timestamp: new Date().toISOString(),
        });
      },
    },
  },

  // 启用开发模式
  development: {
    hmr: true,
    console: true,
  },

  // 未匹配路由时回退
  fetch(req) {
    return new Response("Not Found", { status: 404 });
  },
});

console.log(`🚀 Server running on ${server.url}`);
```

```html title="public/index.html" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>全栈 Bun 应用</title>
    <link rel="stylesheet" href="../src/styles.css" />
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="../src/main.tsx"></script>
  </body>
</html>
```

```tsx title="src/main.tsx" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { createRoot } from "react-dom/client";
import { App } from "./App";

const container = document.getElementById("root")!;
const root = createRoot(container);
root.render(<App />);
```

```tsx title="src/App.tsx" theme={"theme":{"light":"github-light","dark":"dracula"}}
import { useState, useEffect } from "react";

interface User {
  id: number;
  name: string;
  email: string;
  created_at: string;
}

export function App() {
  const [users, setUsers] = useState<User[]>([]);
  const [name, setName] = useState("");
  const [email, setEmail] = useState("");
  const [loading, setLoading] = useState(false);

  const fetchUsers = async () => {
    const response = await fetch("/api/users");
    const data = await response.json();
    setUsers(data);
  };

  const createUser = async (e: React.FormEvent) => {
    e.preventDefault();
    setLoading(true);

    try {
      const response = await fetch("/api/users", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ name, email }),
      });

      if (response.ok) {
        setName("");
        setEmail("");
        await fetchUsers();
      } else {
        const error = await response.json();
        alert(error.error);
      }
    } catch (error) {
      alert("Failed to create user");
    } finally {
      setLoading(false);
    }
  };

  const deleteUser = async (id: number) => {
    if (!confirm("Are you sure?")) return;

    try {
      const response = await fetch(`/api/users/${id}`, {
        method: "DELETE",
      });

      if (response.ok) {
        await fetchUsers();
      }
    } catch (error) {
      alert("Failed to delete user");
    }
  };

  useEffect(() => {
    fetchUsers();
  }, []);

  return (
    <div className="container">
      <h1>用户管理</h1>

      <form onSubmit={createUser} className="form">
        <input type="text" placeholder="姓名" value={name} onChange={e => setName(e.target.value)} required />
        <input type="email" placeholder="邮箱" value={email} onChange={e => setEmail(e.target.value)} required />
        <button type="submit" disabled={loading}>
          {loading ? "正在创建..." : "创建用户"}
        </button>
      </form>

      <div className="users">
        <h2>用户（{users.length}）</h2>
        {users.map(user => (
          <div key={user.id} className="user-card">
            <div>
              <strong>{user.name}</strong>
              <br />
              <span>{user.email}</span>
            </div>
            <button onClick={() => deleteUser(user.id)} className="delete-btn">
              删除
            </button>
          </div>
        ))}
      </div>
    </div>
  );
}
```

```css title="src/styles.css" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

body {
  font-family: -apple-system, BlinkMacSystemFont, sans-serif;
  background: #f5f5f5;
  color: #333;
}

.container {
  max-width: 800px;
  margin: 0 auto;
  padding: 2rem;
}

h1 {
  color: #2563eb;
  margin-bottom: 2rem;
}

.form {
  background: white;
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  margin-bottom: 2rem;
  display: flex;
  gap: 1rem;
  flex-wrap: wrap;
}

.form input {
  flex: 1;
  min-width: 200px;
  padding: 0.75rem;
  border: 1px solid #ddd;
  border-radius: 4px;
}

.form button {
  padding: 0.75rem 1.5rem;
  background: #2563eb;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.form button:hover {
  background: #1d4ed8;
}

.form button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}

.users {
  background: white;
  padding: 1.5rem;
  border-radius: 8px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}

.user-card {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 1rem;
  border-bottom: 1px solid #eee;
}

.user-card:last-child {
  border-bottom: none;
}

.delete-btn {
  padding: 0.5rem 1rem;
  background: #dc2626;
  color: white;
  border: none;
  border-radius: 4px;
  cursor: pointer;
}

.delete-btn:hover {
  background: #b91c1c;
}
```

## 最佳实践

### 项目结构

```
my-app/
├── src/
│   ├── components/
│   │   ├── Header.tsx
│   │   └── UserList.tsx
│   ├── styles/
│   │   ├── globals.css
│   │   └── components.css
│   ├── utils/
│   │   └── api.ts
│   ├── App.tsx
│   └── main.tsx
├── public/
│   ├── index.html
│   ├── dashboard.html
│   └── favicon.ico
├── server/
│   ├── routes/
│   │   ├── users.ts
│   │   └── auth.ts
│   ├── db/
│   │   └── schema.sql
│   └── index.ts
├── bunfig.toml
└── package.json
```

### 基于环境的配置

```ts title="server/config.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
export const config = {
  development: process.env.NODE_ENV !== "production",
  port: process.env.PORT || 3000,
  database: {
    url: process.env.DATABASE_URL || "./dev.db",
  },
  cors: {
    origin: process.env.CORS_ORIGIN || "*",
  },
};
```

### 错误处理

```ts title="server/middleware.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
export function errorHandler(error: Error, req: Request) {
  console.error("Server error:", error);

  if (process.env.NODE_ENV === "production") {
    return Response.json({ error: "Internal server error" }, { status: 500 });
  }

  return Response.json(
    {
      error: error.message,
      stack: error.stack,
    },
    { status: 500 },
  );
}
```

### API 响应辅助函数

```ts title="server/utils.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
export function json(data: any, status = 200) {
  return Response.json(data, { status });
}

export function error(message: string, status = 400) {
  return Response.json({ error: message }, { status });
}

export function notFound(message = "Not found") {
  return error(message, 404);
}

export function unauthorized(message = "Unauthorized") {
  return error(message, 401);
}
```

### 类型安全

```ts title="types/api.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
export interface User {
  id: number;
  name: string;
  email: string;
  created_at: string;
}

export interface CreateUserRequest {
  name: string;
  email: string;
}

export interface ApiResponse<T> {
  data?: T;
  error?: string;
}
```

## 部署

### 生产构建

```bash terminal icon="terminal" theme={"theme":{"light":"github-light","dark":"dracula"}}
# 构建生产版本
bun build --target=bun --production --outdir=dist ./server/index.ts

# 运行生产服务器
NODE_ENV=production bun dist/index.js
```

### Docker 部署

```dockerfile title="Dockerfile" icon="docker" theme={"theme":{"light":"github-light","dark":"dracula"}}
FROM oven/bun:1 as base
WORKDIR /usr/src/app

# 安装依赖
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile

# 复制源代码
COPY . .

# 构建应用
RUN bun build --target=bun --production --outdir=dist ./server/index.ts

# 生产阶段
FROM oven/bun:1-slim
WORKDIR /usr/src/app
COPY --from=base /usr/src/app/dist ./
COPY --from=base /usr/src/app/public ./public

EXPOSE 3000
CMD ["bun", "index.js"]
```

### 环境变量

```ini title=".env.production" icon="file-code" theme={"theme":{"light":"github-light","dark":"dracula"}}
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp
CORS_ORIGIN=https://myapp.com
```

## 从其他框架迁移

### 从 Express + Webpack

```ts title="server.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
// 之前（Express + Webpack）
app.use(express.static("dist"));
app.get("/api/users", (req, res) => {
  res.json(users);
});

// 之后（Bun 全栈）
serve({
  routes: {
    "/": homepage, // 替代 express.static
    "/api/users": {
      GET() {
        return Response.json(users);
      },
    },
  },
});
```

### 从 Next.js API 路由

```ts title="server.ts" icon="https://mintcdn.com/bun-zhcndoc/cnUTwgMuf4cCrwC-/icons/typescript.svg?fit=max&auto=format&n=cnUTwgMuf4cCrwC-&q=85&s=e7767043c9e885c34f2d6c8fe2a95217" theme={"theme":{"light":"github-light","dark":"dracula"}}
// 之前（Next.js）
export default function handler(req, res) {
  if (req.method === 'GET') {
    res.json(users);
  }
}

// 之后（Bun）
"/api/users": {
  GET() { return Response.json(users); }
}
```

## 限制与未来计划

### 当前限制

* `bun build` CLI 尚未集成全栈应用
* 未实现 API 路由自动发现
* 未内置服务器端渲染（SSR）

### 规划功能

* 与 `bun build` CLI 集成
* API 端点的基于文件的路由
* 内置 SSR 支持
* 增强的插件生态

<Note>此部分仍在开发中。功能和 API 可能会随着 Bun 的发展而变动。</Note>
