Bun 완벽 가이드 — 빠른 JS 런타임·패키지 매니저·번들러·테스트 러너 통합

Bun 완벽 가이드 — 빠른 JS 런타임·패키지 매니저·번들러·테스트 러너 통합

이 글의 핵심

Bun은 JavaScriptCore + Zig로 구현된 올인원 JS 런타임·패키지 매니저·번들러·테스트 러너입니다. 2023년 1.0 출시 이후 빠른 개발 속도와 Node.js 호환성을 동시에 끌어올려, 2026년 현재 사이드 프로젝트·서버리스·엣지 함수에서 실용적 Node 대안으로 자리잡았습니다. 이 글은 Bun의 주요 기능·Node 호환성·서버/CLI 개발·번들·테스트·Workspaces·배포 실전을 정리합니다.

설치

# macOS / Linux / WSL
curl -fsSL https://bun.sh/install | bash

# Homebrew
brew install oven-sh/bun/bun

# Windows (공식 지원, PowerShell)
powershell -c "irm bun.sh/install.ps1 | iex"

bun --version

첫 스크립트

// index.ts
const res = await fetch("https://api.github.com")
const data = await res.json() as Record<string, unknown>
console.log(data)
bun run index.ts
# TypeScript 네이티브 실행, 변환·설정 없음

패키지 매니저

bun init                      # 프로젝트 생성
bun add react                 # 의존성 추가
bun add -d typescript         # dev 의존성
bun remove lodash
bun update
bun install                   # package.json 기반 설치
  • bunfig.toml로 설정(레지스트리·캐시·scopes)
  • bun.lockb: 바이너리 lockfile (텍스트 bun.lock도 2025+ 지원)
  • npm/yarn/pnpm 스크립트 모두 bun <script>로 실행

Bun.serve: HTTP 서버

// server.ts
Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url)
    if (url.pathname === "/") return new Response("Hello Bun")
    if (url.pathname === "/api/hello") {
      return Response.json({ message: "Hello JSON" })
    }
    if (url.pathname.startsWith("/file/")) {
      const file = Bun.file(`./public${url.pathname.replace("/file", "")}`)
      return new Response(file)
    }
    return new Response("Not Found", { status: 404 })
  },
  error(e) {
    return new Response(`Error: ${e.message}`, { status: 500 })
  },
})
console.log("http://localhost:3000")
bun server.ts

Express 없이 표준 Web Fetch API로 서버 작성. Node 대비 초당 처리량 3-5배.

Hono + Bun

import { Hono } from "hono"

const app = new Hono()
app.get("/", (c) => c.text("Bun + Hono"))
app.post("/users", async (c) => {
  const body = await c.req.json<{ name: string }>()
  return c.json({ created: body.name }, 201)
})

export default app

bun run --hot app.ts로 HMR 지원 서버.

TypeScript·JSX 네이티브

  • .ts·.tsx·.jsx를 별도 설정 없이 실행
  • tsconfig.jsonpaths 자동 인식
  • import.meta.main으로 진입점 판별
  • 최상위 await 지원

번들러

bun build ./index.ts --outdir ./dist --target browser
bun build ./index.ts --outdir ./dist --target node
bun build ./index.ts --outdir ./dist --target bun
bun build ./cli.ts --compile --outfile ./mycli   # 단일 실행 파일

esbuild와 경쟁할 수준의 속도. 트리 쉐이킹·코드 스플리팅 지원.

단일 바이너리 빌드

bun build ./cli.ts --compile --outfile=./mycli
./mycli

Node 런타임 없이 배포 가능한 단일 실행 파일. 임베디드 Bun 런타임 포함.

테스트 러너 (bun test)

// math.test.ts
import { describe, it, expect, beforeAll } from "bun:test"

beforeAll(() => { /* setup */ })

describe("math", () => {
  it("adds", () => {
    expect(1 + 2).toBe(3)
  })

  it.each([
    [1, 2, 3],
    [5, 5, 10],
  ])("%d + %d = %d", (a, b, r) => {
    expect(a + b).toBe(r)
  })
})
bun test
bun test --coverage
bun test --watch
  • Jest 호환 API (대부분 그대로 동작)
  • Jest 대비 10-30배 빠름
  • 병렬 실행 기본, mocks·spies·timer 모킹 내장

Bun:sqlite

import { Database } from "bun:sqlite"

const db = new Database("app.db")
db.run(`CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)`)

const insert = db.prepare("INSERT INTO users (name) VALUES (?)")
insert.run("Alice")

const rows = db.query("SELECT * FROM users").all()
console.log(rows)

네이티브 SQLite 바인딩. better-sqlite3 수준의 API + 더 빠른 속도.

환경 변수·Secrets

# .env
DATABASE_URL=postgres://localhost/app
// 자동 로드됨
const url = process.env.DATABASE_URL

--env-file로 다중 파일:

bun --env-file=.env --env-file=.env.local run server.ts

Shell ($)

import { $ } from "bun"

await $`ls -la`
const result = await $`git log --oneline -n 5`.text()
console.log(result)

// 파이프
await $`cat package.json | jq .dependencies`

zx 유사 DSL이 bun에 내장. 개발 스크립트에서 shell 사용이 자연스럽습니다.

Workspaces (Monorepo)

// package.json
{
  "name": "my-monorepo",
  "private": true,
  "workspaces": ["apps/*", "packages/*"]
}
bun install   # 루트에서 모든 워크스페이스 동시 설치
bun run --filter=web dev
bun run --filter='./apps/*' build

pnpm/turborepo 대체로 충분한 수준. Turborepo와 조합도 가능.

hot reload·watch

bun --hot server.ts          # HMR 지원 (상태 보존 시도)
bun --watch server.ts        # 변경 시 프로세스 재시작

Nodemon/tsx-watch 대체.

Node 호환 체크리스트

  • fs, path, http, https, stream, events, buffer, crypto, child_process, cluster: 지원
  • worker_threads: 지원
  • process: 대부분 지원, 몇몇 flag 차이
  • N-API 네이티브 모듈: 대부분 동작 (better-sqlite3, sharp 등). 드문 케이스 재빌드 필요
  • Node Streams: 호환
  • pm2·forever: pm2로 Bun 프로세스 관리 가능
  • ESM/CJS: 둘 다 지원. 혼용 가능

CLI 만들기

#!/usr/bin/env bun
// src/cli.ts
import { parseArgs } from "util"

const { values } = parseArgs({
  args: Bun.argv.slice(2),
  options: {
    name: { type: "string", short: "n" },
    verbose: { type: "boolean", short: "v" },
  },
})

console.log(`Hello ${values.name ?? "world"}`)
bun build ./src/cli.ts --compile --outfile=./mycli
./mycli -n JB

단일 바이너리 CLI 배포. brew 등으로 쉽게 배포 가능.

배포

Docker

FROM oven/bun:1.1-alpine AS build
WORKDIR /app
COPY package.json bun.lockb ./
RUN bun install --frozen-lockfile
COPY . .
RUN bun build ./src/server.ts --outdir ./dist --target bun

FROM oven/bun:1.1-alpine
WORKDIR /app
COPY --from=build /app/dist ./dist
COPY --from=build /app/node_modules ./node_modules
ENV NODE_ENV=production
EXPOSE 3000
CMD ["bun", "dist/server.js"]

서버리스

  • Cloudflare Workers: 일부 Bun API 호환되지만 기본은 Workers 자체 런타임. 코드 공유 시 표준 Fetch API 중심 작성
  • Vercel: Functions에서 Bun 런타임 선택 가능
  • AWS Lambda: 커스텀 런타임으로 Bun 바이너리 동봉

성능 가이드

  1. Bun.file(): 가장 빠른 파일 스트리밍
  2. Bun.write(): 대량 쓰기에 최적화
  3. Response·Request 재사용: 웹 표준 API가 최적 경로
  4. FFI (bun:ffi): C 라이브러리 직접 호출 가능, 극한 성능
  5. worker_threads: CPU 바운드는 워커로 분리
  6. Top-level await + ESM: 최신 스타일이 최적화에 유리

예시: 간단한 REST API

import { Database } from "bun:sqlite"

const db = new Database(":memory:")
db.run(`CREATE TABLE posts (id INTEGER PRIMARY KEY, title TEXT NOT NULL)`)

Bun.serve({
  port: 3000,
  async fetch(req) {
    const url = new URL(req.url)

    if (req.method === "GET" && url.pathname === "/posts") {
      return Response.json(db.query("SELECT * FROM posts").all())
    }

    if (req.method === "POST" && url.pathname === "/posts") {
      const body = await req.json() as { title: string }
      const { lastInsertRowid } = db.run("INSERT INTO posts (title) VALUES (?)", [body.title])
      return Response.json({ id: Number(lastInsertRowid), title: body.title }, { status: 201 })
    }

    return new Response("Not Found", { status: 404 })
  },
})

트러블슈팅

네이티브 모듈 빌드 실패

bun pm untrusted로 스크립트 실행 허용 리스트 관리. bun install --force로 재시도.

ERR_MODULE_NOT_FOUND

  • .ts 파일 경로 직접 import 가능, .js 확장자 강제 불필요
  • 타입 선언 없는 CJS 모듈은 declare module 필요할 수 있음

메모리 누수

장기 실행 서버에서 WeakRef·close 관련 누수 발견 시 최신 Bun 버전으로 업그레이드. Bun은 버전당 성능·안정성이 빠르게 개선되고 있음.

bun test가 Jest 테스트 일부 실패

  • Jest globals(jest.fn() 등)는 import { mock } from "bun:test"로 대체
  • @testing-library/jest-dom 같은 확장은 설정 추가 필요

CI에서 느린 설치

  • GitHub Actions 캐시: actions/cache + ~/.bun/install/cache
  • bun install --frozen-lockfile로 lockfile 일관성 보장

체크리스트

  • Node 호환 테스트 — 주요 라이브러리·N-API 모듈 확인
  • bun install/bun test만 먼저 도입해 체감
  • TypeScript·JSX 직접 실행으로 tsx/ts-node 제거
  • bun build --compile로 CLI 단일 바이너리
  • bun:sqlite로 간단한 데이터 저장
  • Docker에 oven/bun 이미지 사용
  • Workspaces로 monorepo 관리
  • 장기 서버는 안정화된 LTS 버전으로

마무리

Bun은 “JavaScript 개발의 고통을 올인원 바이너리로 해결한다”는 야심으로 출발해, 2026년 현재 많은 팀의 선택지에 올라왔습니다. Node의 생태계를 거의 그대로 쓰면서 속도·DX·설치 경험을 극적으로 개선하므로, 큰 리스크 없이 “일단 bun install만 바꿔보자” 수준으로도 바로 가치가 나옵니다. 장기 런타임 전환은 프로젝트 특성에 따라 판단하되, 스크립트·CLI·서버리스·사이드 프로젝트는 지금부터 Bun으로 시작해도 충분히 안전합니다. Zig 기반 성능과 빠른 업데이트 사이클이 Bun의 속도를 앞으로도 보장해줄 것으로 보이며, Node 생태계 전체의 DX 기준을 한 단계 끌어올린 기념비적 도구입니다.

관련 글

  • Node.js 완벽 가이드
  • Deno 완벽 가이드
  • Bun vs Node vs Deno 비교
  • Hono 엣지 프레임워크 가이드