홈시리즈

© 2026 Ki Chang. All rights reserved.

본 블로그의 콘텐츠는 CC BY-NC-SA 4.0 라이선스를 따릅니다.

☕후원하기소개JSON Formatter러닝 대기질개인정보처리방침이용약관

© 2026 Ki Chang. All rights reserved.

콘텐츠: CC BY-NC-SA 4.0

☕후원하기
소개|JSON Formatter|러닝 대기질|개인정보처리방침|이용약관

Node.js만 있는 게 아니다 — Bun과 Deno, 같은 언어 다른 런타임

정기창·2026년 3월 5일

들어가며

JavaScript를 실행하려면 런타임이 필요합니다. 브라우저 밖에서 JS를 실행하는 런타임으로 Node.js가 사실상 표준이었지만, 2018년 Deno, 2022년 Bun이 등장하면서 선택지가 생겼습니다.

세 런타임은 모두 JavaScript를 실행하지만, 내부 구조와 설계 철학이 다릅니다. 같은 코드를 작성해도 어떤 엔진이 해석하고, 어떤 방식으로 I/O를 처리하고, 어떤 도구가 내장되어 있는지가 다릅니다. 이 글에서는 세 런타임을 실용적 관점에서 비교합니다.

런타임이란 — 다시 정리

JavaScript라는 언어 자체에는 파일을 읽거나 HTTP 서버를 띄우는 기능이 없습니다. 1편에서 다뤘듯이, 런타임은 JS 엔진(V8, JSC 등)을 품고 파일 시스템, 네트워크, 타이머 같은 능력을 붙여주는 실행 환경입니다.

브라우저는 V8 + DOM/fetch/window를 제공하고, Node.js는 V8 + fs/http/libuv를 제공합니다. Bun과 Deno도 마찬가지로 각자의 방식으로 이런 능력을 제공하는 런타임입니다. 같은 console.log()는 어디서든 동작하지만, fs.readFile()이나 Deno.readTextFile()처럼 런타임마다 제공하는 API가 다릅니다.

세 런타임의 탄생 배경

Node.js (2009) — 브라우저 밖으로

2009년, Ryan Dahl이 Chrome의 V8 엔진을 브라우저에서 꺼내 서버 사이드 JS를 가능하게 만들었습니다. C로 작성된 libuv 라이브러리로 비동기 I/O를 처리하고(3편), 이벤트 루프 기반의 논블로킹 모델(4편)로 높은 동시성을 달성했습니다. npm 생태계가 폭발적으로 성장하면서, 15년간 브라우저 밖 JS 실행의 사실상 유일한 선택지였습니다.

Deno (2018) — Node.js의 반성

2018년 JSConf EU에서 Ryan Dahl은 "10 Things I Regret About Node.js"를 발표합니다. Node.js를 만든 본인이 후회하는 설계 실수들 — 보안 모델 부재(실행만 하면 파일·네트워크 전부 접근 가능), node_modules의 비대함, CommonJS 모듈 시스템, package.json에 대한 과도한 의존 — 을 바로잡겠다는 철학으로 Deno를 만들었습니다. Rust로 작성하고, TypeScript를 네이티브로 지원하며, 기본적으로 모든 권한을 차단하는 보안 샌드박스를 적용했습니다.

Bun (2022) — 속도에 집중

2022년, Jarred Sumner가 "JavaScript 생태계가 너무 느리다"는 문제의식에서 Bun을 공개합니다. Zig 언어로 작성하고, V8 대신 Apple의 JavaScriptCore(JSC) 엔진을 채택했습니다. 런타임·패키지 매니저·번들러·테스트 러너를 단일 바이너리에 담았고, Node.js 호환성을 유지하면서 속도를 극대화하는 전략을 택했습니다. Deno가 Node.js와의 결별을 선언한 것과 대조적으로, Bun은 기존 생태계를 그대로 품으면서 더 빠르게 실행하는 방향을 선택한 것입니다.

내부 구조 비교

JS 엔진

세 런타임의 가장 근본적인 차이는 JavaScript를 해석하는 엔진입니다.

런타임JS 엔진개발사특징
Node.jsV8GoogleJIT 컴파일 (Ignition + TurboFan), 가장 넓은 생태계 검증
DenoV8GoogleNode.js와 동일한 엔진. GC, JIT 동작도 동일
BunJavaScriptCore (JSC)Apple (WebKit)Safari의 JS 엔진. 시작 시간이 빠르고 메모리 사용량이 적은 경향

Node.js와 Deno는 같은 V8을 쓰므로, 6편에서 다룬 GC — New Space의 Scavenge, Old Space의 Mark-Sweep-Compact — 가 동일하게 적용됩니다. Bun이 사용하는 JSC는 다른 GC 전략을 씁니다. 세대별 수집은 비슷하지만, 동시 마킹과 증분 수집의 구현 방식이 다르고, 일반적으로 시작 시간이 빠르고 메모리 사용량이 적은 경향이 있습니다. 다만 장시간 실행되는 서버 워크로드에서의 JIT 최적화 성숙도는 V8이 앞서 있다는 평가가 많습니다.

I/O 처리

런타임I/O 라이브러리언어특징
Node.jslibuvC크로스 플랫폼 비동기 I/O. epoll(Linux), kqueue(macOS)
DenotokioRustRust 비동기 런타임. 메모리 안전성
Bunio_uring (Linux) / kqueue (macOS)Zig시스템 콜을 직접 최적화. libuv 추상화 레이어 없이 OS에 가깝게

3편에서 살펴봤듯이, libuv는 크로스 플랫폼 추상화를 제공합니다. Linux의 epoll, macOS의 kqueue 등 OS별 차이를 하나의 API로 통일해주지만, 그만큼 추상화 레이어의 오버헤드가 존재합니다. Deno의 tokio는 Rust의 async/await 모델 위에서 동작하며, 메모리 안전성을 언어 차원에서 보장합니다. Bun은 이런 추상화 레이어를 최소화하고 Linux에서는 io_uring, macOS에서는 kqueue를 직접 호출하는 방식으로 속도를 끌어올렸습니다.

런타임 언어

런타임구현 언어의미
Node.jsC++V8과 같은 언어로 긴밀하게 통합
DenoRust메모리 안전성, 동시성 보장
BunZigC 수준 성능 + 메모리 안전성 사이 균형

개발자 경험(DX) 비교

TypeScript 지원

Node.js에서 TypeScript를 실행하려면 tsc, tsx, ts-node 같은 별도 트랜스파일러가 필요합니다. Node.js 22부터 --experimental-strip-types로 타입 제거만 하는 실험적 기능이 추가됐지만, 아직 프로덕션에서 쓰기엔 이릅니다. Deno와 Bun은 .ts 파일을 바로 실행합니다. 별도 설정 없이 deno run app.ts, bun run app.ts가 동작합니다.

패키지 관리

Node.js는 npm, yarn, pnpm 등 별도 패키지 매니저를 사용합니다. node_modules와 package.json이 표준이 된 지 15년이 넘었습니다.

Deno는 처음에 URL import(import { serve } from "https://deno.land/std/http/server.ts")라는 급진적 방식을 택했으나, 생태계와의 단절이 너무 커서 Deno 2.0에서 npm: 지정자와 node_modules 호환을 추가했습니다. 현실과의 타협이었습니다.

Bun은 패키지 매니저를 내장하고 있습니다. npm 레지스트리를 그대로 사용하면서 설치 속도를 대폭 끌어올렸습니다. 다만 락파일이 바이너리(bun.lockb)라 git diff가 불가능하다는 트레이드오프가 있습니다.

내장 도구

기능Node.jsDenoBun
패키지 매니저npm (별도)내장내장
번들러webpack, esbuild 등 (별도)내장 (deno bundle)내장 (Bun.build)
테스트 러너Jest, Vitest 등 (별도)내장 (deno test)내장 (bun test)
린터/포매터ESLint, Prettier (별도)내장 (deno lint/fmt)없음 (별도 필요)
TypeScripttsc (별도)내장내장

보안 모델

Node.js와 Bun은 스크립트를 실행하면 파일 시스템, 네트워크, 환경변수에 제한 없이 접근할 수 있습니다. npm install로 설치한 패키지의 postinstall 스크립트가 무엇이든 실행할 수 있다는 뜻이기도 합니다.

Deno는 정반대입니다. 기본적으로 모든 권한이 차단되어 있고, --allow-read, --allow-net 등 플래그로 명시적으로 허용해야 합니다. Ryan Dahl이 Node.js에서 가장 후회한 점 중 하나가 바로 이 보안 모델의 부재였습니다.

성능 비교

벤치마크 숫자는 넘쳐나지만, 중요한 건 왜 차이가 나는가입니다.

콜드 스타트에서 Bun이 빠른 이유는 JSC의 가벼운 초기화에 있습니다. V8은 JIT 컴파일러(Ignition + TurboFan)의 워밍업이 필요한 반면, JSC는 시작 지연을 줄이는 방향으로 설계되어 있습니다. 서버리스 환경처럼 자주 켜졌다 꺼지는 워크로드에서 이 차이가 체감됩니다.

I/O 처리량에서 Bun이 빠른 이유는 libuv 추상화 레이어를 제거하고 OS 시스템 콜에 더 가깝게 접근하기 때문입니다. 다만 5편에서 본 것처럼, 실제 웹 서버의 병목은 대부분 DB 쿼리나 외부 API 호출 같은 I/O 대기 시간이지, 런타임 자체의 처리 속도가 아닙니다.

패키지 설치 속도에서 Bun이 30배 빠르다는 벤치마크는 캐시가 없는 클린 설치 기준입니다. 같은 머신에서 반복 설치하면 pnpm도 글로벌 store를 재사용하므로 차이가 크게 줄어듭니다.

현대 프론트엔드에서의 체감 차이는 의외로 작습니다. Next.js dev 서버의 시간 대부분은 SWC(Rust)나 Turbopack(Rust)이 차지하고, JS 런타임이 관여하는 비중은 극히 적습니다. 런타임을 바꿔도 이 부분은 변하지 않습니다.

npm 생태계 호환성

세 런타임 모두 npm 패키지를 쓸 수 있지만 호환성 수준이 다릅니다.

Node.js는 본가입니다. npm에 등록된 모든 패키지가 Node.js를 기준으로 만들어졌으므로 100% 호환됩니다.

Bun은 Drop-in replacement를 지향합니다. 대부분의 패키지가 그대로 동작하지만, node-gyp으로 컴파일하는 네이티브 C++ 모듈(bcrypt 등)은 지원되지 않아 순수 JS 대안으로 교체해야 하는 경우가 있습니다. child_process 같은 Node.js 내장 API도 Bun.spawn()이라는 자체 API를 권장하는 등 미묘한 차이가 존재합니다.

Deno는 2.0에서 npm: 지정자를 도입하며 호환성을 대폭 개선했지만, 네이티브 애드온(N-API)이나 Node.js 전용 API(process.nextTick 등)에서 여전히 제약이 있습니다. Node.js와 결별했다가 다시 돌아온 만큼, 호환성 개선이 현재 진행형입니다.

어떤 런타임을 선택할 것인가

상황추천이유
엔터프라이즈 프로덕션, 레거시 유지보수Node.js15년간 검증된 안정성, 가장 넓은 생태계
새 프로젝트, 속도·DX 우선Bun빠른 시작, 올인원 도구, Node.js 호환
보안 최우선, 웹 표준 준수Deno기본 샌드박스, Web API 표준 지향
모노레포, 대규모 의존성Bun패키지 설치 속도가 압도적
기존 Node.js 프로젝트Node.js (유지)마이그레이션 비용 대비 이점 평가 필요

정리

세 런타임은 같은 JavaScript를 실행하지만, 설계 철학이 다릅니다. Node.js는 안정성과 생태계, Deno는 보안과 웹 표준, Bun은 속도와 개발자 경험. 어떤 것이 "최고"인지가 아니라, 프로젝트의 맥락에 따라 선택이 달라집니다.

이 시리즈에서 다뤄온 이벤트 루프, 6개 페이즈, 가비지 컬렉션, 스트림, 모듈 시스템 같은 개념은 런타임이 달라져도 그대로 적용됩니다. 비동기 I/O의 원리를 이해하고 있다면, libuv가 tokio로 바뀌든 io_uring으로 바뀌든 핵심 멘탈 모델은 동일합니다. 런타임은 도구일 뿐, 중요한 건 그 도구가 왜 그렇게 동작하는지를 이해하는 것입니다.

JavaScriptNode.jsBunDeno런타임

관련 글

Bun으로 갈아탈까? 실제 모노레포로 검증해본 결과

1633개 패키지를 가진 실제 프로덕션 모노레포에서 Bun 마이그레이션을 검토했습니다. 네이티브 모듈 호환성부터 체감 속도 예측, 워크트리 설치 시간 실측까지, 벤치마크 숫자가 아닌 현실적인 분석을 정리했습니다.

관련도 92%

Node.js 소스코드를 직접 열어봤습니다 — 런타임, V8, libuv의 실체 (1편)

면접 준비를 하다가 "Node.js가 뭔가요?"라는 질문에 제대로 답할 수 없다는 걸 깨달았습니다. 런타임이 뭔지, V8과 libuv가 각각 무슨 역할인지, 실제 Node.js GitHub 소스코드를 열어서 os.hostname() 한 줄이 OS까지 도달하는 과정을 추적해봤습니다.

관련도 91%

CommonJS와 ESM — require와 import는 어떻게 다르게 동작하는가 (8편)

Node.js의 두 가지 모듈 시스템 — CommonJS의 require와 ESM의 import가 내부에서 어떻게 다르게 동작하는지 추적합니다. 로딩 시점, 캐싱, 순환 참조 처리의 차이를 살펴봅니다.

관련도 89%