
Bun: Node.js를 대체할 수 있을까?
npm install이 3분 걸리던 프로젝트가 bun install로 10초. 빠르다는 건 알겠는데, 실제로 프로덕션에 쓸 수 있을까?

npm install이 3분 걸리던 프로젝트가 bun install로 10초. 빠르다는 건 알겠는데, 실제로 프로덕션에 쓸 수 있을까?
버튼을 눌렀는데 부모 DIV까지 클릭되는 현상. 이벤트는 물방울처럼 위로 올라갑니다(Bubbling). 반대로 내려오는 캡처링(Capturing)도 있죠.

느리다고 느껴서 감으로 최적화했는데 오히려 더 느려졌다. 프로파일러로 병목을 정확히 찾는 법을 배운 이야기.

텍스트에서 바이너리로(HTTP/2), TCP에서 UDP로(HTTP/3). 한 줄로서기 대신 병렬처리 가능해진 웹의 진화. 구글이 주도한 QUIC 프로토콜 이야기.

HTML 파싱부터 DOM, CSSOM 생성, 렌더 트리, 레이아웃(Reflow), 페인트(Repaint), 그리고 합성(Composite)까지. 브라우저가 화면을 그리는 6단계 과정과 치명적인 렌더링 성능 최적화(CRP) 가이드.

프로젝트 clone 받고 npm install 돌리면 커피 한잔 타러 갔다 와야 했다. 3분. dependencies가 많은 날엔 5분도 넘었다. 그러다 Bun을 써봤는데 10초 만에 끝났다. 처음엔 뭔가 잘못된 줄 알았다. "이게 진짜 다 설치된 거야?" package.json 보고, node_modules 폴더 열어보고, 실제로 프로젝트 돌려보고서야 믿었다.
빠르다는 건 알겠는데, 실제로 쓸 수 있을까? Node.js 생태계와 호환이 될까? 프로덕션 환경에 배포해도 괜찮을까? 이런 질문들이 꼬리를 물었다.
결론부터 말하면, Bun은 단순히 "빠른 Node.js"가 아니었다. 런타임, 번들러, 패키지 매니저, 테스트 러너를 하나로 합친 올인원 툴킷이었다. 마치 스위스 아미 나이프처럼. 그리고 대부분의 케이스에서 실제로 쓸 수 있었다.
처음엔 Bun을 "npm의 빠른 대안" 정도로 생각했다. 틀렸다. Bun은 JavaScript 개발 경험 전체를 재설계한 플랫폼이었다.
Node.js 환경에서 프로젝트를 시작하려면 뭐가 필요했나? npm이나 pnpm으로 패키지 관리하고, esbuild나 webpack으로 번들링하고, Jest나 Vitest로 테스트하고, nodemon이나 tsx로 dev server 돌리고... 각각의 도구마다 설정 파일이 필요했다.
Bun은 이 모든 걸 하나로 합쳤다. 그것도 훨씬 빠르게. 마치 짐을 여러 가방에 나눠 담고 하나씩 옮기던 것을, 하나의 큰 트렁크에 담아 한 번에 옮기는 것처럼.
Node.js는 C++로 작성됐고 V8 엔진을 쓴다. Bun은 Zig로 작성됐고 JavaScriptCore(Safari의 엔진)를 쓴다. 이 차이가 체감 속도로 나타났다.
# Node.js 프로젝트
$ time npm install
real 3m 12s
# 같은 프로젝트를 Bun으로
$ time bun install
real 0m 9s
20배 빠르다. 단순히 패키지 다운로드만 빠른 게 아니었다. 서버 시작도, 테스트 실행도, 번들링도 모두 빠랐다.
Bun은 Node.js API를 대부분 지원한다. fs, path, http 같은 기본 모듈들은 그대로 작동한다. 기존 Node.js 프로젝트 대부분이 수정 없이 돌아간다.
하지만 Bun만의 네이티브 API가 진짜 게임 체인저였다.
// Node.js 방식
import express from 'express';
const app = express();
app.get('/', (req, res) => res.send('Hello'));
app.listen(3000);
// Bun 네이티브 방식
Bun.serve({
port: 3000,
fetch(req) {
return new Response('Hello');
},
});
Bun.serve()는 express 없이도 HTTP 서버를 띄운다. 그것도 훨씬 빠르게. 벤치마크를 돌려봤더니 초당 요청 처리량이 Node.js + Express보다 3배 높았다. 의존성도 줄고 성능도 오르는, 일석이조였다.
파일 I/O도 더 직관적이었다.
// Node.js 방식
import fs from 'fs/promises';
const text = await fs.readFile('file.txt', 'utf-8');
const buffer = await fs.readFile('image.png');
// Bun 방식
const file = Bun.file('file.txt');
const text = await file.text();
const buffer = await file.arrayBuffer();
Bun.file()은 File API를 네이티브로 지원한다. 브라우저 코드처럼 쓸 수 있어서 익숙했다. 추가로 SQLite도 내장됐다.
import { Database } from 'bun:sqlite';
const db = new Database('mydb.sqlite');
db.run('CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT)');
db.run('INSERT INTO users (name) VALUES (?)', 'Alice');
const users = db.query('SELECT * FROM users').all();
별도 패키지 설치 없이 바로 SQLite를 쓸 수 있다. 프로토타입 만들 때 정말 편했다.
bun install은 npm, yarn, pnpm과 호환된다. package.json을 그대로 쓰되, 속도가 훨씬 빠르다.
bun install # package.json 기반 설치
bun add express # npm install express와 동일
bun add -d typescript # devDependencies에 추가
bun remove lodash # 패키지 제거
lockfile도 다르다. bun.lockb라는 바이너리 포맷을 쓴다. 텍스트 기반 lockfile보다 훨씬 빠르게 읽고 쓸 수 있다. Git에서도 diff가 의미 없긴 하지만, 어차피 lockfile diff를 자세히 보는 경우는 드물었다.
Global 캐시도 영리했다. 같은 패키지를 여러 프로젝트에서 쓰면 하드링크로 연결해서 디스크 공간을 아낀다. pnpm과 비슷한 방식인데, 더 빠르다.
bun build ./src/index.ts --outdir ./dist
이게 전부다. 설정 파일 없이 바로 번들링된다. TypeScript, JSX, CSS 모두 자동으로 처리한다.
// 고급 설정이 필요하면
await Bun.build({
entrypoints: ['./src/index.ts'],
outdir: './dist',
minify: true,
sourcemap: 'external',
splitting: true, // code splitting
target: 'browser',
});
esbuild만큼 빠르면서 설정이 더 간단했다. webpack에서 넘어오니 config 파일 200줄이 사라진 기분이었다.
// math.test.ts
import { expect, test, describe } from 'bun:test';
describe('Math', () => {
test('addition', () => {
expect(1 + 1).toBe(2);
});
test('async operation', async () => {
const result = await Promise.resolve(42);
expect(result).toBe(42);
});
});
Jest API와 거의 동일하다. 기존 테스트 코드를 거의 그대로 쓸 수 있다. 하지만 속도는 비교가 안 됐다.
# Jest
$ time npm test
Tests: 156 passed
Time: 8.3s
# Bun
$ time bun test
Tests: 156 passed
Time: 0.7s
10배 이상 빠르다. 특히 watch mode에서 테스트를 계속 돌릴 때 이 차이가 크게 느껴졌다. 코드 저장하면 바로 테스트 결과가 나왔다.
Node.js 생태계 호환을 목표로 하지만, 100%는 아니다.
잘 작동하는 것들:실제로 프로젝트 몇 개를 마이그레이션해봤는데, 80%는 그냥 됐고, 15%는 약간 수정이 필요했고, 5%는 대체 패키지를 찾아야 했다.
마치 Windows에서 Mac으로 넘어가는 것처럼. 대부분의 앱은 다 있는데, 가끔 없는 것도 있는.
#!/usr/bin/env bun
// deploy.ts
const response = await fetch('https://api.service.com/deploy', {
method: 'POST',
body: await Bun.file('./dist.zip').arrayBuffer(),
});
console.log(await response.json());
Node.js로 만든 스크립트들을 Bun으로 바꿨더니 실행 속도가 2-3배 빨라졌다. Cold start가 빠르기 때문이다.
3. 개발 도구이런 경우엔 Node.js를 유지하거나, 해당 기능만 별도 서비스로 분리하는 게 낫다.
실제로 Next.js 프로젝트를 Bun으로 옮겨봤다.
# 1. Bun 설치
curl -fsSL https://bun.sh/install | bash
# 2. node_modules 삭제하고 재설치
rm -rf node_modules
bun install
# 3. 실행
bun run dev
이게 전부였다. 대부분의 경우 이것만으로 작동했다.
문제가 생긴 패키지가 있었다면:
// package.json
{
"dependencies": {
// "bcrypt": "^5.1.0", // native addon 사용
"bcryptjs": "^2.4.3" // pure JS 구현으로 대체
}
}
Pure JavaScript 구현체로 바꿔주면 됐다. 성능이 약간 떨어지긴 하지만, 개발 환경에서는 큰 문제가 안 됐다.
프로덕션에서는? 아직은 신중하게 접근했다. 새로운 마이크로서비스나 사이드 프로젝트에 먼저 적용해봤다. 문제없이 돌아가면 점진적으로 확대하는 전략.
Bun은 "Node.js를 대체할 수 있나?"라는 질문보다 "JavaScript 개발 경험을 어떻게 개선할 수 있나?"라는 질문에 대한 답이었다.
npm, webpack, Jest, nodemon... 이 모든 도구를 각각 배우고 설정하는 대신, 하나의 통합된 도구로 모든 걸 할 수 있다. 그것도 훨씬 빠르게.
새 프로젝트를 시작한다면? Bun을 쓸 것이다. 기존 프로젝트를 마이그레이션한다면? 리스크를 따져볼 것이다. 프로덕션 배포는? 작은 것부터 시도해볼 것이다.
빠르다는 건 단순히 시간을 아끼는 것 이상이었다. 개발 흐름이 끊기지 않는 것. 테스트 돌리는 동안 딴짓하지 않아도 되는 것. 번들링 기다리면서 집중력이 흐트러지지 않는 것.
Bun은 개발자의 시간을 존중하는 도구였다. 그리고 그게 결국 더 나은 소프트웨어를 만드는 시작점이었다.