기본 설정
const server = Bun.serve({
// `routes` 는 Bun v1.2.3+ 필요
routes: {
// 정적 경로
"/api/status": new Response("OK"),
// 동적 경로
"/users/:id": req => {
return new Response(`안녕하세요 사용자 ${req.params.id}!`);
},
// HTTP 메서드별 핸들러
"/api/posts": {
GET: () => new Response("게시물 목록"),
POST: async req => {
const body = await req.json();
return Response.json({ created: true, ...body });
},
},
// "/api/" 로 시작하고 그렇지 않으면 일치하지 않는 모든 경로에 대한 와일드카드 경로
"/api/*": Response.json({ message: "찾을 수 없음" }, { status: 404 }),
// /blog/hello 에서 /blog/hello/world 로 리디렉션
"/blog/hello": Response.redirect("/blog/hello/world"),
// 파일을 메모리로 지연 로드하여 제공
"/favicon.ico": Bun.file("./favicon.ico"),
},
// (선택 사항) 일치하지 않는 경로의 폴백:
// Bun 버전 < 1.2.3 인 경우 필요
fetch(req) {
return new Response("찾을 수 없음", { status: 404 });
},
});
console.log(`서버 실행 중: ${server.url}`);HTML import
Bun 은 서버 측 및 클라이언트 측 코드를 모두 갖춘 풀스택 애플리케이션을 가능하게 하는 서버 코드에 HTML 파일을 직접 import 하는 것을 지원합니다. HTML import 는 두 가지 모드로 작동합니다.
개발 (bun --hot): 자산은 런타임에 수요에 따라 번들링되어 빠른 반복 개발 경험을 위한 핫 모듈 교체 (HMR) 를 가능하게 합니다. 프론트엔드 코드를 변경하면 브라우저가 전체 페이지 새로고침 없이 자동으로 업데이트됩니다.
프로덕션 (bun build): bun build --target=bun 으로 빌드할 때 import index from "./index.html" 문은 번들된 모든 클라이언트 자산이 포함된 사전 빌드된 매니페스트 객체로 해결됩니다. Bun.serve 는 이 매니페스트를 사용하여 제로 런타임 번들링 오버헤드로 최적화된 자산을 제공합니다. 이는 프로덕션에 배포하는 데 이상적입니다.
import myReactSinglePageApp from "./index.html";
Bun.serve({
routes: {
"/": myReactSinglePageApp,
},
});HTML import 는 HTML 만 제공하는 것이 아닙니다. Bun 의 번들러, JavaScript 트랜스파일러 및 CSS 파서를 사용하여 구축된 전체 기능의 프론트엔드 번들러, 트랜스파일러 및 툴킷입니다. 이를 사용하여 React, TypeScript, Tailwind CSS 등으로 전체 기능의 프론트엔드를 구축할 수 있습니다.
HTML import 를 사용하여 풀스택 애플리케이션을 구축하는 방법에 대한 완전한 가이드는 자세한 예시 및 모범 사례와 함께 /docs/bundler/fullstack 을 참조하세요.
구성
port 및 hostname 변경
서버가 수신할 포트와 호스트네임을 구성하려면 옵션 객체에서 port 와 hostname 을 설정합니다.
Bun.serve({
port: 8080, // 기본값은 $BUN_PORT, $PORT, $NODE_PORT 아니면 3000
hostname: "mydomain.com", // 기본값은 "0.0.0.0"
fetch(req) {
return new Response("404!");
},
});사용 가능한 포트를 무작위로 선택하려면 port 를 0 으로 설정합니다.
const server = Bun.serve({
port: 0, // 무작위 포트
fetch(req) {
return new Response("404!");
},
});
// server.port 는 무작위로 선택된 포트입니다
console.log(server.port);서버 객체의 port 속성에 액세스하거나 url 속성에 액세스하여 선택한 포트를 볼 수 있습니다.
console.log(server.port); // 3000
console.log(server.url); // http://localhost:3000기본 포트 구성
Bun 은 기본 포트를 구성하기 위한 여러 옵션과 환경 변수를 지원합니다. 기본 포트는 port 옵션이 설정되지 않은 경우 사용됩니다.
--portCLI 플래그
bun --port=4002 server.tsBUN_PORT환경 변수
BUN_PORT=4002 bun server.tsPORT환경 변수
PORT=4002 bun server.tsNODE_PORT환경 변수
NODE_PORT=4002 bun server.tsUnix 도메인 소켓
Unix 도메인 소켓 에서 수신하려면 소켓 경로와 함께 unix 옵션을 전달합니다.
Bun.serve({
unix: "/tmp/my-socket.sock", // 소켓 경로
fetch(req) {
return new Response(`404!`);
},
});추상 네임스페이스 소켓
Bun 은 Linux 추상 네임스페이스 소켓을 지원합니다. 추상 네임스페이스 소켓을 사용하려면 unix 경로 앞에 null 바이트를 붙입니다.
Bun.serve({
unix: "\0my-abstract-socket", // 추상 네임스페이스 소켓
fetch(req) {
return new Response(`404!`);
},
});Unix 도메인 소켓과 달리 추상 네임스페이스 소켓은 파일시스템에 바인딩되지 않으며 소켓의 마지막 참조가 닫힐 때 자동으로 제거됩니다.
idleTimeout
유휴 시간 제한을 구성하려면 Bun.serve 에서 idleTimeout 필드를 설정합니다.
Bun.serve({
// 10 초:
idleTimeout: 10,
fetch(req) {
return new Response("Bun!");
},
});이는 서버가 연결을 닫기 전에 연결이 유휴 상태일 수 있는 최대 시간입니다. 데이터가 송수신되지 않으면 연결이 유휴 상태입니다.
export default 구문
지금까지 이 페이지의 예시는 명시적 Bun.serve API 를 사용했습니다. Bun 은 대체 구문도 지원합니다.
import { type Serve } from "bun";
export default {
fetch(req) {
return new Response("Bun!");
},
} satisfies Serve;서버 옵션을 Bun.serve 에 전달하는 대신 export default 합니다. 이 파일은 그대로 실행할 수 있습니다. Bun 이 fetch 핸들러를 포함하는 default export 가 있는 파일을 보면 내부적으로 Bun.serve 에 전달합니다.
핫 경로 리로딩
server.reload() 를 사용하여 서버 재시작 없이 경로를 업데이트합니다.
const server = Bun.serve({
routes: {
"/api/version": () => Response.json({ version: "1.0.0" }),
},
});
// 다운타임 없이 새 경로 배포
server.reload({
routes: {
"/api/version": () => Response.json({ version: "2.0.0" }),
},
});서버 수명 주기 메서드
server.stop()
서버가 새 연결을 수락하지 않도록 중지하려면:
const server = Bun.serve({
fetch(req) {
return new Response("안녕하세요!");
},
});
// 정상적으로 서버 중지 (진행 중인 요청 대기)
await server.stop();
// 모든 활성 연결을 강제로 중지하고 닫기
await server.stop(true);기본적으로 stop() 은 진행 중인 요청과 WebSocket 연결이 완료되도록 합니다. true 를 전달하면 모든 연결을 즉시 종료합니다.
server.ref() 및 server.unref()
서버가 Bun 프로세스를 계속 유지하는지 제어합니다.
// 서버가 유일하게 실행 중인 경우 프로세스 유지 안 함
server.unref();
// 기본 동작 복원 - 프로세스 유지
server.ref();server.reload()
서버를 재시작하지 않고 서버의 핸들러를 업데이트합니다.
const server = Bun.serve({
routes: {
"/api/version": Response.json({ version: "v1" }),
},
fetch(req) {
return new Response("v1");
},
});
// 새 핸들러로 업데이트
server.reload({
routes: {
"/api/version": Response.json({ version: "v2" }),
},
fetch(req) {
return new Response("v2");
},
});이는 개발 및 핫 리로딩에 유용합니다. fetch, error 및 routes 만 업데이트할 수 있습니다.
요청별 제어
server.timeout(Request, seconds)
개별 요청에 대한 사용자 정의 유휴 시간 제한을 설정합니다.
const server = Bun.serve({
async fetch(req, server) {
// 이 요청에 대해 60 초 시간 제한 설정
server.timeout(req, 60);
// 본체를 보내는 데 60 초 이상 걸리면 요청이 중단됨
await req.text();
return new Response("완료!");
},
});요청에 대한 시간 제한을 비활성화하려면 0 을 전달합니다.
server.requestIP(Request)
클라이언트 IP 및 포트 정보를 가져옵니다.
const server = Bun.serve({
fetch(req, server) {
const address = server.requestIP(req);
if (address) {
return new Response(`클라이언트 IP: ${address.address}, 포트: ${address.port}`);
}
return new Response("알 수 없는 클라이언트");
},
});닫힌 요청 또는 Unix 도메인 소켓의 경우 null 을 반환합니다.
서버 메트릭
server.pendingRequests 및 server.pendingWebSockets
내장 카운터를 사용하여 서버 활동을 모니터링합니다.
const server = Bun.serve({
fetch(req, server) {
return new Response(
`활성 요청: ${server.pendingRequests}\n` + `활성 WebSocket: ${server.pendingWebSockets}`,
);
},
});server.subscriberCount(topic)
WebSocket 토픽의 구독자 수를 가져옵니다.
const server = Bun.serve({
fetch(req, server) {
const chatUsers = server.subscriberCount("chat");
return new Response(`${chatUsers}명의 사용자가 채팅 중`);
},
websocket: {
message(ws) {
ws.subscribe("chat");
},
},
});벤치마크
아래는 각 들어오는 Request 에 Bun! 로 응답하는 간단한 HTTP 서버의 Bun 과 Node.js 구현입니다.
Bun.serve({
fetch(req: Request) {
return new Response("Bun!");
},
port: 3000,
});require("http")
.createServer((req, res) => res.end("Bun!"))
.listen(8080);Bun.serve 서버는 Linux 에서 Node.js 보다 초당 약 2.5 배 더 많은 요청을 처리할 수 있습니다.
| 런타임 | 초당 요청 수 |
|---|---|
| Node 16 | ~64,000 |
| Bun | ~160,000 |
실용적인 예시: REST API
다음은 제로 종속성으로 Bun 의 라우터를 사용한 기본 데이터베이스 기반 REST API 입니다.
import type { Post } from "./types.ts";
import { Database } from "bun:sqlite";
const db = new Database("posts.db");
db.exec(`
CREATE TABLE IF NOT EXISTS posts (
id TEXT PRIMARY KEY,
title TEXT NOT NULL,
content TEXT NOT NULL,
created_at TEXT NOT NULL
)
`);
Bun.serve({
routes: {
// 게시물 목록
"/api/posts": {
GET: () => {
const posts = db.query("SELECT * FROM posts").all();
return Response.json(posts);
},
// 게시물 생성
POST: async req => {
const post: Omit<Post, "id" | "created_at"> = await req.json();
const id = crypto.randomUUID();
db.query(
`INSERT INTO posts (id, title, content, created_at)
VALUES (?, ?, ?, ?)`,
).run(id, post.title, post.content, new Date().toISOString());
return Response.json({ id, ...post }, { status: 201 });
},
},
// ID 로 게시물 가져오기
"/api/posts/:id": req => {
const post = db.query("SELECT * FROM posts WHERE id = ?").get(req.params.id);
if (!post) {
return new Response("찾을 수 없음", { status: 404 });
}
return Response.json(post);
},
},
error(error) {
console.error(error);
return new Response("내부 서버 오류", { status: 500 });
},
});export interface Post {
id: string;
title: string;
content: string;
created_at: string;
}참조
interface Server extends Disposable {
/**
* 새 연결 수락을 중지합니다.
* @param closeActiveConnections true 이면 즉시 모든 연결 종료
* @returns 서버가 중지되면 해결되는 Promise
*/
stop(closeActiveConnections?: boolean): Promise<void>;
/**
* 서버를 재시작하지 않고 핸들러를 업데이트합니다.
* fetch 와 error 핸들러만 업데이트할 수 있습니다.
*/
reload(options: Serve): void;
/**
* 실행 중인 서버에 요청을 합니다.
* 테스트 또는 내부 라우팅에 유용합니다.
*/
fetch(request: Request | string): Response | Promise<Response>;
/**
* HTTP 요청을 WebSocket 연결로 업그레이드합니다.
* @returns 성공하면 true, 실패하면 false
*/
upgrade<T = undefined>(
request: Request,
options?: {
headers?: Bun.HeadersInit;
data?: T;
},
): boolean;
/**
* 토픽에 구독한 모든 WebSocket 클라이언트에 메시지를 게시합니다.
* @returns 전송된 바이트, 드롭되면 0, 백프레셔 적용되면 -1
*/
publish(
topic: string,
data: string | ArrayBufferView | ArrayBuffer | SharedArrayBuffer,
compress?: boolean,
): ServerWebSocketSendStatus;
/**
* 토픽에 구독한 WebSocket 클라이언트 수를 가져옵니다.
*/
subscriberCount(topic: string): number;
/**
* 클라이언트 IP 주소와 포트를 가져옵니다.
* @param 닫힌 요청 또는 Unix 소켓의 경우 null
*/
requestIP(request: Request): SocketAddress | null;
/**
* 요청에 대한 사용자 정의 유휴 시간 제한을 설정합니다.
* @param seconds 초 단위 시간 제한, 0 은 비활성화
*/
timeout(request: Request, seconds: number): void;
/**
* 서버 실행 중 프로세스를 유지합니다.
*/
ref(): void;
/**
* 서버가 유일하게 실행 중인 경우 프로세스 종료 허용
*/
unref(): void;
/** 진행 중인 HTTP 요청 수 */
readonly pendingRequests: number;
/** 활성 WebSocket 연결 수 */
readonly pendingWebSockets: number;
/** 프로토콜, 호스트네임 및 포트를 포함한 서버 URL */
readonly url: URL;
/** 서버가 수신 중인 포트 */
readonly port: number;
/** 서버가 바인딩된 호스트네임 */
readonly hostname: string;
/** 서버가 개발 모드인지 여부 */
readonly development: boolean;
/** 서버 인스턴스 식별자 */
readonly id: string;
}
interface WebSocketHandler<T = undefined> {
/** 바이트 단위 최대 WebSocket 메시지 크기 */
maxPayloadLength?: number;
/** 백프레셔 적용 전 대기열 메시지의 바이트 */
backpressureLimit?: number;
/** 백프레셔 제한에 도달했을 때 연결을 닫을지 여부 */
closeOnBackpressureLimit?: boolean;
/** 백프레셔가 해제될 때 호출됨 */
drain?(ws: ServerWebSocket<T>): void | Promise<void>;
/** 유휴 시간 제한까지의 초 */
idleTimeout?: number;
/** 메시지당 deflate 압축 활성화 */
perMessageDeflate?:
| boolean
| {
compress?: WebSocketCompressor | boolean;
decompress?: WebSocketCompressor | boolean;
};
/** 연결 유지를 위해 ping 프레임 전송 */
sendPings?: boolean;
/** 서버가 자체 게시 메시지를 수신하는지 여부 */
publishToSelf?: boolean;
/** 연결이 열렸을 때 호출됨 */
open?(ws: ServerWebSocket<T>): void | Promise<void>;
/** 메시지를 수신했을 때 호출됨 */
message(ws: ServerWebSocket<T>, message: string | Buffer): void | Promise<void>;
/** 연결이 닫혔을 때 호출됨 */
close?(ws: ServerWebSocket<T>, code: number, reason: string): void | Promise<void>;
/** ping 프레임을 수신했을 때 호출됨 */
ping?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;
/** pong 프레임을 수신했을 때 호출됨 */
pong?(ws: ServerWebSocket<T>, data: Buffer): void | Promise<void>;
}
interface TLSOptions {
/** 인증 기관 체인 */
ca?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;
/** 서버 인증서 */
cert?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;
/** DH 매개변수 파일 경로 */
dhParamsFile?: string;
/** 개인 키 */
key?: string | Buffer | BunFile | Array<string | Buffer | BunFile>;
/** TLS 메모리 사용량 감소 */
lowMemoryMode?: boolean;
/** 개인 키 패스프레이즈 */
passphrase?: string;
/** OpenSSL 옵션 플래그 */
secureOptions?: number;
/** SNI 를 위한 서버 이름 */
serverName?: string;
}