Skip to content

정적 경로, 매개변수 및 와일드카드의 경우 routes 속성을 사용하거나 fetch 메서드로 일치하지 않는 요청을 처리하여 Bun.serve() 에 경로를 추가할 수 있습니다.

Bun.serve() 의 라우터는 uWebSocket 의 트리 기반 접근 방식 을 기반으로 하여 SIMD 가속 경로 매개변수 디코딩JavaScriptCore 구조 캐싱 을 추가하여 현대 하드웨어가 허용하는 성능 한계를 끌어올립니다.

기본 설정

ts
Bun.serve({
  routes: {
    "/": () => new Response("홈"),
    "/api": () => Response.json({ success: true }),
    "/users": async () => Response.json({ users: [] }),
  },
  fetch() {
    return new Response("일치하지 않는 경로");
  },
});

Bun.serve() 의 경로는 BunRequest(Request 를 확장) 를 받고 Response 또는 Promise<Response> 를 반환합니다. 이를 통해 HTTP 요청을 보내고 받는 데 동일한 코드를 더 쉽게 사용할 수 있습니다.

ts
// 간략화를 위해 단순화됨
interface BunRequest<T extends string> extends Request {
  params: Record<T, string>;
  readonly cookies: CookieMap;
}

비동기 경로

Async/await

경로 핸들러에서 async/await 를 사용하여 Promise<Response> 를 반환할 수 있습니다.

ts
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> 를 반환할 수도 있습니다.

ts
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);
      });
    },
  },
});

경로 우선순위

경로는 구체성에 따라 순서대로 일치합니다.

  1. 정확히 일치하는 경로 (/users/all)
  2. 매개변수가 있는 경로 (/users/:id)
  3. 와일드카드 경로 (/users/*)
  4. 전역 catch-all (/*)
ts
Bun.serve({
  routes: {
    // 가장 구체적인 것부터
    "/api/users/me": () => new Response("현재 사용자"),
    "/api/users/:id": req => new Response(`사용자 ${req.params.id}`),
    "/api/*": () => new Response("API catch-all"),
    "/*": () => new Response("전역 catch-all"),
  },
});

타입 안전 경로 매개변수

TypeScript 는 경로 매개변수를 문자열 리터럴로 전달할 때 구문 분석하므로 에디터에서 request.params 에 접근할 때 자동 완성을 표시합니다.

ts
import type { BunRequest } from "bun";

Bun.serve({
  routes: {
    // 문자열 리터럴로 전달될 때 TypeScript 는 params 의 형태를 인식합니다
    "/orgs/:orgId/repos/:repoId": req => {
      const { orgId, repoId } = req.params;
      return Response.json({ orgId, repoId });
    },

    "/orgs/:orgId/repos/:repoId/settings": (
      // 선택 사항: BunRequest 에 명시적으로 타입을 전달할 수 있습니다
      req: BunRequest<"/orgs/:orgId/repos/:repoId/settings">,
    ) => {
      const { orgId, repoId } = req.params;
      return Response.json({ orgId, repoId });
    },
  },
});

퍼센트 인코딩된 경로 매개변수 값은 자동으로 디코딩됩니다. 유니코드 문자가 지원됩니다. 잘못된 유니코드는 유니코드 대체 문자 &0xFFFD; 로 대체됩니다.

정적 응답

경로는 Response 객체 (핸들러 함수 없이) 일 수도 있습니다. Bun.serve() 는 제로 할당 디스패치를 위해 이를 최적화합니다. 상태 확인, 리디렉션 및 고정 콘텐츠에 이상적입니다.

ts
Bun.serve({
  routes: {
    // 상태 확인
    "/health": new Response("OK"),
    "/ready": new Response("Ready", {
      headers: {
        // 사용자 정의 헤더 전달
        "X-Ready": "1",
      },
    }),

    // 리디렉션
    "/blog": Response.redirect("https://bun.com/blog"),

    // API 응답
    "/api/config": Response.json({
      version: "1.0.0",
      env: "production",
    }),
  },
});

정적 응답은 초기화 후 서버 객체의 수명 동안 추가 메모리를 할당하지 않습니다. 일반적으로 Response 객체를 수동으로 반환하는 것보다 최소 15% 성능 향상을 기대할 수 있습니다.

정적 경로 응답은 서버 객체의 수명 동안 캐시됩니다. 정적 경로를 다시 로드하려면 server.reload(options) 를 호출합니다.

파일 응답 vs 정적 응답

경로에서 파일을 제공할 때 파일 콘텐츠를 버퍼링하거나 직접 제공하는지에 따라 두 가지 동작이 있습니다.

ts
Bun.serve({
  routes: {
    // 정적 경로 - 콘텐츠는 시작 시 메모리에 버퍼링됨
    "/logo.png": new Response(await Bun.file("./logo.png").bytes()),

    // 파일 경로 - 콘텐츠는 각 요청 시 파일시스템에서 읽힘
    "/download.zip": new Response(Bun.file("./download.zip")),
  },
});

정적 경로 (new Response(await file.bytes())) 는 시작 시 콘텐츠를 메모리에 버퍼링합니다.

  • 요청 중 파일시스템 I/O 없음 - 콘텐츠가 전적으로 메모리에서 제공됨
  • ETag 지원 - 캐싱을 위해 ETag 를 자동으로 생성하고 유효성을 검사함
  • If-None-Match - 클라이언트 ETag 가 일치할 때 304 Not Modified 반환
  • 404 처리 없음 - 누락된 파일은 런타임 404 가 아닌 시작 오류를 발생시킴
  • 메모리 사용량 - 전체 파일 콘텐츠가 RAM 에 저장됨
  • 용도: 작은 정적 자산, API 응답, 자주 액세스하는 파일

파일 경로 (new Response(Bun.file(path))) 는 요청당 파일시스템에서 읽습니다.

  • 각 요청 시 파일시스템 읽기 - 파일 존재 확인 및 콘텐츠 읽기
  • 내장 404 처리 - 파일이 없거나 액세스할 수 없는 경우 404 Not Found 반환
  • Last-Modified 지원 - 파일 수정 시간을 If-Modified-Since 헤더에 사용
  • If-Modified-Since - 파일이 클라이언트의 캐시된 버전 이후 변경되지 않았을 때 304 Not Modified 반환
  • 범위 요청 지원 - Content-Range 헤더로 부분 콘텐츠 요청을 자동으로 처리
  • 스트리밍 전송 - 효율적인 메모리 사용을 위해 백프레셔 처리가 있는 버퍼드 리더 사용
  • 메모리 효율적 - 전체 파일이 아닌 전송 중 작은 청크만 버퍼링
  • 용도: 대용량 파일, 동적 콘텐츠, 사용자 업로드, 자주 변경되는 파일

파일 스트리밍

파일을 스트리밍하려면 본체가 BunFile 객체인 Response 객체를 반환합니다.

ts
Bun.serve({
  fetch(req) {
    return new Response(Bun.file("./hello.txt"));
  },
});

Bun.file 객체의 slice(start, end) 메서드를 사용하여 파일의 일부를 보낼 수 있습니다. 이는 자동으로 Response 객체의 Content-RangeContent-Length 헤더를 설정합니다.

ts
Bun.serve({
  fetch(req) {
    // `Range` 헤더 파싱
    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]

    // 파일의 일부 반환
    const bigFile = Bun.file("./big-video.mp4");
    return new Response(bigFile.slice(start, end));
  },
});

fetch 요청 핸들러

fetch 핸들러는 경로에 의해 일치되지 않은 들어오는 요청을 처리합니다. Request 객체를 받고 Response 또는 Promise<Response> 를 반환합니다.

ts
Bun.serve({
  fetch(req) {
    const url = new URL(req.url);
    if (url.pathname === "/") return new Response("홈 페이지!");
    if (url.pathname === "/blog") return new Response("블로그!");
    return new Response("404!");
  },
});

fetch 핸들러는 async/await 를 지원합니다.

ts
import { sleep, serve } from "bun";

serve({
  async fetch(req) {
    const start = performance.now();
    await sleep(10);
    const end = performance.now();
    return new Response(`${end - start}ms 동안 대기`);
  },
});

Promise 기반 응답도 지원됩니다.

ts
Bun.serve({
  fetch(req) {
    // 요청을 다른 서버로 전달
    return fetch("https://example.com");
  },
});

fetch 핸들러에서 Server 객체에 액세스할 수도 있습니다. 이는 fetch 함수에 전달되는 두 번째 인수입니다.

ts
// `server` 는 fetch 의 두 번째 인수로 전달됩니다.
const server = Bun.serve({
  fetch(req, server) {
    const ip = server.requestIP(req);
    return new Response(`IP 는 ${ip.address}입니다`);
  },
});

Bun by www.bunjs.com.cn 편집