npm install 3분이 10초로
프로젝트 clone 받고 npm install 돌리면 커피 한잔 타러 갔다 와야 했다. 3분. dependencies가 많은 날엔 5분도 넘었다. 그러다 Bun을 써봤는데 10초 만에 끝났다. 처음엔 뭔가 잘못된 줄 알았다. "이게 진짜 다 설치된 거야?" package.json 보고, node_modules 폴더 열어보고, 실제로 프로젝트 돌려보고서야 믿었다.
빠르다는 건 알겠는데, 실제로 쓸 수 있을까? Node.js 생태계와 호환이 될까? 프로덕션 환경에 배포해도 괜찮을까? 이런 질문들이 꼬리를 물었다.
결론부터 말하면, Bun은 단순히 "빠른 Node.js"가 아니었다. 런타임, 번들러, 패키지 매니저, 테스트 러너를 하나로 합친 올인원 툴킷이었다. 마치 스위스 아미 나이프처럼. 그리고 대부분의 케이스에서 실제로 쓸 수 있었다.
깨달은 것 - Bun은 생태계가 아니라 플랫폼이다
처음엔 Bun을 "npm의 빠른 대안" 정도로 생각했다. 틀렸다. Bun은 JavaScript 개발 경험 전체를 재설계한 플랫폼이었다.
Node.js 환경에서 프로젝트를 시작하려면 뭐가 필요했나? npm이나 pnpm으로 패키지 관리하고, esbuild나 webpack으로 번들링하고, Jest나 Vitest로 테스트하고, nodemon이나 tsx로 dev server 돌리고... 각각의 도구마다 설정 파일이 필요했다.
Bun은 이 모든 걸 하나로 합쳤다. 그것도 훨씬 빠르게. 마치 짐을 여러 가방에 나눠 담고 하나씩 옮기던 것을, 하나의 큰 트렁크에 담아 한 번에 옮기는 것처럼.
속도의 비밀 - Zig와 JavaScriptCore
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의 실체 - 4가지 도구가 하나로
1. 런타임 - Node.js 호환 + 네이티브 API
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를 쓸 수 있다. 프로토타입 만들 때 정말 편했다.
2. 패키지 매니저 - npm보다 20배 빠른 설치
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과 비슷한 방식인데, 더 빠르다.
3. 번들러 - esbuild 속도에 zero-config
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줄이 사라진 기분이었다.
4. 테스트 러너 - Jest 호환, 훨씬 빠름
// 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%는 아니다.
잘 작동하는 것들:
- Express, Fastify 같은 웹 프레임워크
- Prisma, Drizzle 같은 ORM
- Zod, Yup 같은 validation 라이브러리
- 대부분의 유틸리티 라이브러리 (lodash, date-fns 등)
가끔 문제가 생기는 것들:
- Native addon을 쓰는 패키지 (bcrypt, sharp 등)
- Node.js 내부 API에 깊이 의존하는 패키지
- 일부 오래된 패키지들
실제로 프로젝트 몇 개를 마이그레이션해봤는데, 80%는 그냥 됐고, 15%는 약간 수정이 필요했고, 5%는 대체 패키지를 찾아야 했다.
마치 Windows에서 Mac으로 넘어가는 것처럼. 대부분의 앱은 다 있는데, 가끔 없는 것도 있는.
언제 써야 하나 - 새 프로젝트부터
Bun이 완벽한 경우:
1. 새로운 프로젝트
- 레거시 없이 시작할 수 있다
- 최신 패키지들은 대부분 Bun과 호환된다
- 개발 속도가 압도적으로 빠르다
2. CLI 도구나 스크립트
#!/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. 개발 도구
- 로컬 dev server
- 빌드 파이프라인
- 테스트 환경
Bun을 아직 쓰지 말아야 하는 경우:
1. 엔터프라이즈 프로덕션
- 검증된 시간이 짧다 (2023년 9월 1.0 출시)
- 장애 발생 시 레퍼런스가 적다
- 팀 전체가 Node.js에 익숙하다면 리스크가 크다
2. Native addon이 필수인 경우
- 이미지 처리 (sharp)
- 암호화 (bcrypt의 native 버전)
- 특정 하드웨어 제어
이런 경우엔 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은 개발자의 시간을 존중하는 도구였다. 그리고 그게 결국 더 나은 소프트웨어를 만드는 시작점이었다.