정적 경로, 매개변수 및 와일드카드의 경우 routes 속성을 사용하거나 fetch 메서드로 일치하지 않는 요청을 처리하여 Bun.serve() 에 경로를 추가할 수 있습니다.
Bun.serve() 의 라우터는 uWebSocket 의 트리 기반 접근 방식 을 기반으로 하여 SIMD 가속 경로 매개변수 디코딩 과 JavaScriptCore 구조 캐싱 을 추가하여 현대 하드웨어가 허용하는 성능 한계를 끌어올립니다.
기본 설정
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 요청을 보내고 받는 데 동일한 코드를 더 쉽게 사용할 수 있습니다.
// 간략화를 위해 단순화됨
interface BunRequest<T extends string> extends Request {
params: Record<T, string>;
readonly cookies: CookieMap;
}비동기 경로
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);
});
},
},
});경로 우선순위
경로는 구체성에 따라 순서대로 일치합니다.
- 정확히 일치하는 경로 (
/users/all) - 매개변수가 있는 경로 (
/users/:id) - 와일드카드 경로 (
/users/*) - 전역 catch-all (
/*)
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 에 접근할 때 자동 완성을 표시합니다.
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() 는 제로 할당 디스패치를 위해 이를 최적화합니다. 상태 확인, 리디렉션 및 고정 콘텐츠에 이상적입니다.
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 정적 응답
경로에서 파일을 제공할 때 파일 콘텐츠를 버퍼링하거나 직접 제공하는지에 따라 두 가지 동작이 있습니다.
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 객체를 반환합니다.
Bun.serve({
fetch(req) {
return new Response(Bun.file("./hello.txt"));
},
});Bun.file 객체의 slice(start, end) 메서드를 사용하여 파일의 일부를 보낼 수 있습니다. 이는 자동으로 Response 객체의 Content-Range 및 Content-Length 헤더를 설정합니다.
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> 를 반환합니다.
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 를 지원합니다.
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 기반 응답도 지원됩니다.
Bun.serve({
fetch(req) {
// 요청을 다른 서버로 전달
return fetch("https://example.com");
},
});fetch 핸들러에서 Server 객체에 액세스할 수도 있습니다. 이는 fetch 함수에 전달되는 두 번째 인수입니다.
// `server` 는 fetch 의 두 번째 인수로 전달됩니다.
const server = Bun.serve({
fetch(req, server) {
const ip = server.requestIP(req);
return new Response(`IP 는 ${ip.address}입니다`);
},
});