Skip to content

기본 설정

ts
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 는 이 매니페스트를 사용하여 제로 런타임 번들링 오버헤드로 최적화된 자산을 제공합니다. 이는 프로덕션에 배포하는 데 이상적입니다.

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

Bun.serve({
  routes: {
    "/": myReactSinglePageApp,
  },
});

HTML import 는 HTML 만 제공하는 것이 아닙니다. Bun 의 번들러, JavaScript 트랜스파일러 및 CSS 파서를 사용하여 구축된 전체 기능의 프론트엔드 번들러, 트랜스파일러 및 툴킷입니다. 이를 사용하여 React, TypeScript, Tailwind CSS 등으로 전체 기능의 프론트엔드를 구축할 수 있습니다.

HTML import 를 사용하여 풀스택 애플리케이션을 구축하는 방법에 대한 완전한 가이드는 자세한 예시 및 모범 사례와 함께 /docs/bundler/fullstack 을 참조하세요.


구성

porthostname 변경

서버가 수신할 포트와 호스트네임을 구성하려면 옵션 객체에서 porthostname 을 설정합니다.

ts
Bun.serve({
  port: 8080, // 기본값은 $BUN_PORT, $PORT, $NODE_PORT 아니면 3000
  hostname: "mydomain.com", // 기본값은 "0.0.0.0"
  fetch(req) {
    return new Response("404!");
  },
});

사용 가능한 포트를 무작위로 선택하려면 port0 으로 설정합니다.

ts
const server = Bun.serve({
  port: 0, // 무작위 포트
  fetch(req) {
    return new Response("404!");
  },
});

// server.port 는 무작위로 선택된 포트입니다
console.log(server.port);

서버 객체의 port 속성에 액세스하거나 url 속성에 액세스하여 선택한 포트를 볼 수 있습니다.

ts
console.log(server.port); // 3000
console.log(server.url); // http://localhost:3000

기본 포트 구성

Bun 은 기본 포트를 구성하기 위한 여러 옵션과 환경 변수를 지원합니다. 기본 포트는 port 옵션이 설정되지 않은 경우 사용됩니다.

  • --port CLI 플래그
sh
bun --port=4002 server.ts
  • BUN_PORT 환경 변수
sh
BUN_PORT=4002 bun server.ts
  • PORT 환경 변수
sh
PORT=4002 bun server.ts
  • NODE_PORT 환경 변수
sh
NODE_PORT=4002 bun server.ts

Unix 도메인 소켓

Unix 도메인 소켓 에서 수신하려면 소켓 경로와 함께 unix 옵션을 전달합니다.

ts
Bun.serve({
  unix: "/tmp/my-socket.sock", // 소켓 경로
  fetch(req) {
    return new Response(`404!`);
  },
});

추상 네임스페이스 소켓

Bun 은 Linux 추상 네임스페이스 소켓을 지원합니다. 추상 네임스페이스 소켓을 사용하려면 unix 경로 앞에 null 바이트를 붙입니다.

ts
Bun.serve({
  unix: "\0my-abstract-socket", // 추상 네임스페이스 소켓
  fetch(req) {
    return new Response(`404!`);
  },
});

Unix 도메인 소켓과 달리 추상 네임스페이스 소켓은 파일시스템에 바인딩되지 않으며 소켓의 마지막 참조가 닫힐 때 자동으로 제거됩니다.


idleTimeout

유휴 시간 제한을 구성하려면 Bun.serve 에서 idleTimeout 필드를 설정합니다.

ts
Bun.serve({
  // 10 초:
  idleTimeout: 10,

  fetch(req) {
    return new Response("Bun!");
  },
});

이는 서버가 연결을 닫기 전에 연결이 유휴 상태일 수 있는 최대 시간입니다. 데이터가 송수신되지 않으면 연결이 유휴 상태입니다.


export default 구문

지금까지 이 페이지의 예시는 명시적 Bun.serve API 를 사용했습니다. Bun 은 대체 구문도 지원합니다.

ts
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() 를 사용하여 서버 재시작 없이 경로를 업데이트합니다.

ts
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()

서버가 새 연결을 수락하지 않도록 중지하려면:

ts
const server = Bun.serve({
  fetch(req) {
    return new Response("안녕하세요!");
  },
});

// 정상적으로 서버 중지 (진행 중인 요청 대기)
await server.stop();

// 모든 활성 연결을 강제로 중지하고 닫기
await server.stop(true);

기본적으로 stop() 은 진행 중인 요청과 WebSocket 연결이 완료되도록 합니다. true 를 전달하면 모든 연결을 즉시 종료합니다.

server.ref()server.unref()

서버가 Bun 프로세스를 계속 유지하는지 제어합니다.

ts
// 서버가 유일하게 실행 중인 경우 프로세스 유지 안 함
server.unref();

// 기본 동작 복원 - 프로세스 유지
server.ref();

server.reload()

서버를 재시작하지 않고 서버의 핸들러를 업데이트합니다.

ts
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, errorroutes 만 업데이트할 수 있습니다.


요청별 제어

server.timeout(Request, seconds)

개별 요청에 대한 사용자 정의 유휴 시간 제한을 설정합니다.

ts
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 및 포트 정보를 가져옵니다.

ts
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.pendingRequestsserver.pendingWebSockets

내장 카운터를 사용하여 서버 활동을 모니터링합니다.

ts
const server = Bun.serve({
  fetch(req, server) {
    return new Response(
      `활성 요청: ${server.pendingRequests}\n` + `활성 WebSocket: ${server.pendingWebSockets}`,
    );
  },
});

server.subscriberCount(topic)

WebSocket 토픽의 구독자 수를 가져옵니다.

ts
const server = Bun.serve({
  fetch(req, server) {
    const chatUsers = server.subscriberCount("chat");
    return new Response(`${chatUsers}명의 사용자가 채팅 중`);
  },
  websocket: {
    message(ws) {
      ws.subscribe("chat");
    },
  },
});

벤치마크

아래는 각 들어오는 RequestBun! 로 응답하는 간단한 HTTP 서버의 Bun 과 Node.js 구현입니다.

ts
Bun.serve({
  fetch(req: Request) {
    return new Response("Bun!");
  },
  port: 3000,
});
ts
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 입니다.

ts
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 });
  },
});
ts
export interface Post {
  id: string;
  title: string;
  content: string;
  created_at: string;
}

참조

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

Bun by www.bunjs.com.cn 편집