Skip to content

시작하려면 HTML 파일을 가져와서 Bun.serve()routes 옵션에 전달하세요.

ts
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
bun run app.ts

HTML 라우트

HTML 가져오기를 라우트로

웹은 HTML 로 시작하며 Bun 의 풀스택 개발 서버도 마찬가지입니다.

프론트엔드의 진입점을 지정하려면 HTML 파일을 JavaScript/TypeScript/TSX/JSX 파일로 가져오세요.

ts
import dashboard from "./dashboard.html";
import homepage from "./index.html";

이 HTML 파일은 Bun.serve() 에 전달할 수 있는 Bun 개발 서버의 라우트로 사용됩니다.

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

  fetch(req) {
    // ... api 요청
  },
});

/dashboard 또는 / 에 요청하면 Bun 은 HTML 파일의 <script><link> 태그를 자동으로 번들링하고 정적 라우트로 노출한 후 결과를 제공합니다.

HTML 처리 예제

이러한 index.html 파일은:

html
<!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
<!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 를 가져와서 앱을 렌더링하세요.

ts
import dashboard from "../public/dashboard.html";
import { serve } from "bun";

serve({
  routes: {
    "/": dashboard,
  },
  async fetch(req) {
    // ...api 요청
    return new Response("hello world");
  },
});
tsx
import { createRoot } from 'react-dom/client';
import App from './app';

const container = document.getElementById('root');
const root = createRoot(container!);
root.render(<App />);
html
<!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
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>
  );
}

개발 모드

로컬에서 빌드할 때 Bun.serve() 에서 development: true 를 설정하여 개발 모드를 활성화하세요.

ts
import homepage from "./index.html";
import dashboard from "./dashboard.html";

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

  development: true,

  fetch(req) {
    // ... api 요청
  },
});

개발 모드 기능

developmenttrue 일 때 Bun 은 다음을 수행합니다:

  • devtools 가 원본 소스 코드를 표시할 수 있도록 응답에 SourceMap 헤더 포함
  • 축소 비활성화
  • .html 파일에 대한 각 요청 시 자산 재번들링
  • 핫 모듈 리로딩 활성화 (hmr: false 가 설정되지 않은 경우)
  • 브라우저의 콘솔 로그를 터미널로 에코

고급 개발 구성

Bun.serve() 는 브라우저의 콘솔 로그를 터미널로 에코하는 것을 지원합니다.

활성화하려면 Bun.serve() 의 development 객체에서 console: true 를 전달하세요.

ts
import homepage from "./index.html";

Bun.serve({
  // development 는 객체일 수도 있습니다.
  development: {
    // 핫 모듈 리로딩 활성화
    hmr: true,

    // 브라우저의 콘솔 로그를 터미널로 에코
    console: true,
  },

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

console: true 가 설정되면 Bun 은 브라우저에서 터미널로 콘솔 로그를 스트리밍합니다. 이는 HMR 의 기존 WebSocket 연결을 재사용하여 로그를 전송합니다.

개발 vs 프로덕션

기능개발프로덕션
소스맵✅ 활성화❌ 비활성화
축소❌ 비활성화✅ 활성화
핫 리로딩✅ 활성화❌ 비활성화
자산 번들링🔄 각 요청 시💾 캐시됨
콘솔 로깅🖥️ 브라우저 → 터미널❌ 비활성화
오류 세부정보📝 상세🔒 최소

프로덕션 모드

핫 리로딩과 development: true 는 빠르게 반복하는 데 도움이 되지만 프로덕션에서는 서버가 가능한 한 빠르고 외부 종속성이 적어야 합니다.

사전 빌드 번들링 (권장)

Bun v1.2.17 부터 Bun.build 또는 bun build 를 사용하여 풀스택 애플리케이션을 사전에 번들링할 수 있습니다.

bash
bun build --target=bun --production --outdir=dist ./src/index.ts

Bun 의 번들러가 서버 측 코드에서 HTML 가져오기를 감지하면 참조된 JavaScript/TypeScript/TSX/JSX 및 CSS 파일을 Bun.serve() 가 자산을 제공하는 데 사용할 수 있는 매니페스트 객체로 번들링합니다.

ts
import { serve } from "bun";
import index from "./index.html";

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

런타임 번들링

빌드 단계를 추가하는 것이 너무 복잡할 때 Bun.serve() 에서 development: false 를 설정할 수 있습니다.

이것은 다음을 수행합니다:

  • 번들링된 자산의 인메모리 캐싱 활성화. Bun 은 .html 파일에 대한 첫 번째 요청 시 자산을 지연 번들링하고 서버가 재시작될 때까지 결과를 메모리에 캐시합니다.
  • Cache-Control 헤더 및 ETag 헤더 활성화
  • JavaScript/TypeScript/TSX/JSX 파일 축소
ts
import { serve } from "bun";
import homepage from "./index.html";

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

  // 프로덕션 모드
  development: false,
});

API 라우트

HTTP 메서드 핸들러

HTTP 메서드 핸들러로 API 엔드포인트를 정의하세요:

ts
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
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
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: "데이터 처리됨",
          page: parseInt(page),
          authenticated: !!auth,
        });
      },
    },
  },
});

플러그인

Bun 의 번들러 플러그인은 정적 라우트를 번들링할 때도 지원됩니다.

Bun.serve 에 대한 플러그인을 구성하려면 bunfig.toml[serve.static] 섹션에 plugins 배열을 추가하세요.

TailwindCSS 플러그인

tailwindcss 패키지 및 bun-plugin-tailwind 플러그인을 설치하고 추가하여 TailwindCSS 를 사용할 수 있습니다.

bash
bun add tailwindcss bun-plugin-tailwind
toml
[serve.static]
plugins = ["bun-plugin-tailwind"]

이렇게 하면 HTML 및 CSS 파일에서 TailwindCSS 유틸리티 클래스를 사용할 수 있습니다. 필요한 것은 tailwindcss 를 어딘가에서 가져오는 것입니다:

html
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="tailwindcss" />
  </head>
  <!-- 나머지 HTML... -->
</html>

또는 CSS 파일에서 TailwindCSS 를 가져올 수 있습니다:

css
@import "tailwindcss";

.custom-class {
  @apply bg-red-500 text-white;
}
html
<!doctype html>
<html>
  <head>
    <link rel="stylesheet" href="./style.css" />
  </head>
  <!-- 나머지 HTML... -->
</html>

커스텀 플러그인

유효한 번들러 플러그인 객체 (본질적으로 namesetup 필드가 있는 객체) 를 내보내는 모든 JS 파일 또는 모듈을 plugins 배열에 배치할 수 있습니다:

toml
[serve.static]
plugins = ["./my-plugin-implementation.ts"]
ts
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 에서는 아직 지원되지 않습니다.

인라인 환경 변수

Bun 은 빌드 시점에 프론트엔드 JavaScript 및 TypeScript 의 process.env.* 참조를 실제 값으로 바꿀 수 있습니다. bunfig.toml 에서 env 옵션을 구성하세요:

toml
[serve.static]
env = "PUBLIC_*"  # PUBLIC_ 로 시작하는 환경 변수만 인라인 (권장)
# env = "inline"  # 모든 환경 변수 인라인
# env = "disable" # 환경 변수 교체 비활성화 (기본값)

참고

이것은 리터럴 process.env.FOO 참조에만 작동하며 import.meta.env 또는 const env = process.env; env.FOO 와 같은 간접 액세스에는 작동하지 않습니다.

환경 변수가 설정되지 않으면 브라우저에서 ReferenceError: process is not defined 와 같은 런타임 오류가 발생할 수 있습니다.

빌드 타임 구성 및 예제에 대한 자세한 내용은 HTML 및 정적 사이트 문서 를 참조하세요.

작동 방식

Bun 은 HTMLRewriter 를 사용하여 HTML 파일에서 <script><link> 태그를 스캔하고 Bun 번들러의 진입점으로 사용하며 JavaScript/TypeScript/TSX/JSX 및 CSS 파일에 대한 최적화된 번들을 생성한 후 결과를 제공합니다.

처리 파이프라인

1. script 처리

  • <script> 태그의 TypeScript, JSX 및 TSX 트랜스파일
  • 가져온 종속성 번들링
  • 디버깅을 위한 소스맵 생성
  • Bun.serve() 에서 developmenttrue 가 아닐 때 축소
html
<script type="module" src="./counter.tsx"></script>

2. 처리

  • CSS 가져오기 및 <link> 태그 처리
  • CSS 파일 연결
  • url 및 자산 경로를 다시 작성하여 URL 에 콘텐츠 주소 해시 포함
html
<link rel="stylesheet" href="./styles.css" />

3. 및 자산 처리

  • 자산에 대한 링크가 다시 작성되어 URL 에 콘텐츠 주소 해시 포함
  • CSS 파일의 작은 자산은 data: URL 로 인라인되어 네트워크를 통해 전송되는 HTTP 요청 총 수 감소

4. HTML 다시 쓰기

  • 모든 <script> 태그를 URL 에 콘텐츠 주소 해시가 있는 단일 <script> 태그로 결합
  • 모든 <link> 태그를 URL 에 콘텐츠 주소 해시가 있는 단일 <link> 태그로 결합
  • 새 HTML 파일 출력

5. 제공

  • 번들러의 모든 출력 파일은 Bun.serve() 에서 Response 객체를 static 에 전달할 때 내부적으로 사용하는 메커니즘과 동일한 메커니즘을 사용하여 정적 라우트로 노출됩니다.
  • 이는 Bun.build 가 HTML 파일을 처리하는 방식과 유사하게 작동합니다.

전체 예제

다음은 완전한 풀스택 애플리케이션 예제입니다:

ts
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: "이메일이 이미 존재합니다" }, { 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: "사용자를 찾을 수 없습니다" }, { 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: "사용자를 찾을 수 없습니다" }, { 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.url}`);
html
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1" />
    <title>Fullstack Bun App</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
import { createRoot } from "react-dom/client";
import { App } from "./App";

const container = document.getElementById("root")!;
const root = createRoot(container);
root.render(<App />);
tsx
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("사용자 생성 실패");
    } finally {
      setLoading(false);
    }
  };

  const deleteUser = async (id: number) => {
    if (!confirm("삭제하시겠습니까?")) return;

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

      if (response.ok) {
        await fetchUsers();
      }
    } catch (error) {
      alert("사용자 삭제 실패");
    }
  };

  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
* {
  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
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
export function errorHandler(error: Error, req: Request) {
  console.error("서버 오류:", error);

  if (process.env.NODE_ENV === "production") {
    return Response.json({ error: "내부 서버 오류" }, { status: 500 });
  }

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

API 응답 도우미

ts
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 = "찾을 수 없습니다") {
  return error(message, 404);
}

export function unauthorized(message = "인증되지 않음") {
  return error(message, 401);
}

타입 안전성

ts
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
# 프로덕션용 빌드
bun build --target=bun --production --outdir=dist ./server/index.ts

# 프로덕션 서버 실행
NODE_ENV=production bun dist/index.js

Docker 배포

dockerfile
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
NODE_ENV=production
PORT=3000
DATABASE_URL=postgresql://user:pass@localhost:5432/myapp
CORS_ORIGIN=https://myapp.com

다른 프레임워크에서 마이그레이션

Express + Webpack 에서

ts
// 이전 (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
// 이전 (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

이것은 진행 중인 작업입니다. Bun 이 계속 발전함에 따라 기능과 API 가 변경될 수 있습니다.

Bun by www.bunjs.com.cn 편집