Node.js 런타임이란 무엇인가
Node.js는 간단히 말해 자바스크립트 파일을 실행해 주는 프로그램이다. 컴퓨터에 설치해 두고 쓰는 런타임이라고 보면 된다. 터미널에서 node script.js라고 치는 순간, Node는 해당 파일을 읽어 크롬에서 쓰이는 구글의 V8 엔진에 넘겨 실행한다. 여기에 V8 혼자서는 할 수 없는 일들을 처리하기 위한 방대한 API 라이브러리가 얹혀 있는 구조다.
V8은 자바스크립트를 실행할 줄만 안다. 파일을 열거나, TCP 소켓으로 포트를 열거나, 프로세스를 띄우거나, 환경 변수를 읽는 일은 V8의 몫이 아니다. 이런 기능은 모두 Node가 C++로 구현해 내장 모듈 형태로 자바스크립트에 노출해 준다.
node --version
node script.js
Node는 언어가 아닙니다. 프레임워크도 아니죠. Node.js는 런타임입니다. V8 엔진, 표준 라이브러리, 모듈 시스템, 그리고 이벤트 루프를 한데 묶어놓은 것, 그게 전부예요.
첫 번째 스크립트 작성하기
어떤 .js 파일이든 그대로 Node 프로그램이 됩니다. 보일러플레이트도, main 함수도 필요 없어요:
console.log은 브라우저에서 쓰던 것과 완전히 똑같이 동작합니다. 템플릿 리터럴, Date, 배열, 프로미스 등 이미 알고 있는 언어 기능들은 전부 V8 엔진에서 오기 때문에 동작이 동일합니다. Node.js에서 달라지는 건 언어 자체가 아니라, 언어 주변에 놓인 환경입니다.
Node.js에만 존재하는 전역 객체
브라우저에는 window, document, localStorage, fetch가 있죠. 반면 Node.js는 서버 사이드 런타임이라는 성격에 맞춰 전혀 다른 전역 객체들을 제공합니다.
process는 지금 실행 중인 Node 프로세스를 가리킵니다. 환경 변수(process.env), 명령줄 인자(process.argv), 그리고 프로세스를 종료하는 메서드(process.exit(1)) 같은 것들이 여기 담겨 있습니다.__filename과__dirname은 현재 파일과 그 파일이 속한 폴더의 절대 경로를 알려줍니다. (단, ES 모듈에서는 이 둘이 없고 대신import.meta.url을 씁니다.)global은 최상위 객체로, 브라우저의window에 해당하는 Node 쪽 전역 객체입니다.
반면 document나 window는 존재하지 않습니다. 무심코 갖다 쓰면 바로 ReferenceError가 터지죠. 브라우저용으로 만들어진 라이브러리를 Node에서 그대로 돌리려 할 때 가장 먼저 마주치는 신호이기도 합니다.
명령줄 인자와 환경 변수 다루기
Node가 실제로 가장 많이 쓰이는 영역 — CLI 도구, 빌드 스크립트, 서버 — 은 결국 인자와 환경 변수를 읽어오는 일에서 시작합니다. 이 둘 모두 process 객체에 들어 있습니다:
process.argv는 배열입니다. 앞의 두 원소는 각각 Node 실행 파일 경로와 스크립트 파일 경로이기 때문에, 실제로 전달된 인자는 인덱스 2부터 시작합니다. process.env는 환경 변수를 담은 평범한 객체로, 여기서 NODE_ENV, PORT, API 키 같은 값을 읽어오는 건 흔히 쓰는 방식입니다.
Node.js 내장 모듈
Node.js에는 표준 라이브러리가 기본으로 포함되어 있고, require(CommonJS)나 import(ESM)로 불러올 수 있습니다. 모듈 이름 앞에 node: 접두사를 붙이면 내장 모듈이라는 점이 명확하게 드러납니다:
가장 자주 쓰게 될 내장 모듈들:
node:fs— 파일 읽기/쓰기. async/await 스타일로 쓰고 싶다면node:fs/promises를 사용하세요.node:path— 파일 경로를 OS에 상관없이 합치고, 해석하고, 파싱합니다.node:http/node:https— HTTP 서버를 띄우거나 요청을 보낼 때 씁니다.node:url— URL을 파싱하거나 조립할 때 사용합니다.node:os— 실행 중인 머신 정보를 가져옵니다.node:crypto— 해시, 난수 바이트, 암호화 기능을 제공합니다.
이 모듈들은 따로 설치할 필요가 없습니다. Node에 기본으로 포함되어 있거든요. 이외에 필요한 건 전부 npm에서 받아오면 됩니다.
Node.js 이벤트 루프, 짧게 훑어보기
Node는 자바스크립트 코드를 단일 스레드에서 실행하지만, 동시에 여러 작업을 처리합니다. 그 비밀이 바로 이벤트 루프예요. 파일 읽기, HTTP 요청, 타이머처럼 비동기 작업을 호출하면, Node는 실제 작업을 OS(또는 내부 스레드 풀)에 넘겨버리고 다음 코드를 계속 실행합니다. 작업이 끝나면 콜백이 큐에 쌓이고, 현재 실행 중인 코드가 마무리되는 시점에 루프가 그 콜백을 꺼내 처리하는 구조입니다.
출력 순서는 1, 4, 2, 3입니다. 먼저 동기 코드가 실행되고, 그다음 마이크로태스크(resolve된 프로미스), 마지막으로 타이머가 실행되죠. CPU를 많이 쓰는 느린 반복문이 서버 전체를 멈춰버리는 이유가 바로 여기에 있습니다. 여러분이 작성한 코드를 돌리는 스레드는 단 하나뿐이거든요. Node.js의 동시성은 연산이 아니라 I/O를 위한 것입니다.
간단한 HTTP 서버 만들기
지금까지 배운 내용의 결실은, 동작하는 웹 서버를 단 몇 줄로 만들 수 있다는 점입니다.
프레임워크도, 외부 의존성도 필요 없습니다. createServer는 요청이 들어올 때마다 실행될 함수를 인자로 받고, listen은 이벤트 루프를 돌려 들어오는 연결을 처리하기 시작합니다. 실무에서는 보통 이 위에 Express나 Fastify를 얹어 쓰지만, 그 밑바닥은 결국 같은 내장 http 모듈입니다.
Node.js vs 브라우저
어떤 기능이 서로 공유되고, 어떤 건 그렇지 않은지 확실히 짚고 넘어갈 필요가 있습니다.
| 양쪽 모두 | Node 전용 | 브라우저 전용 |
|---|---|---|
언어 자체 기능 (클래스, 프로미스, async/await) | fs, http, process, __dirname | window, document, DOM |
console.log | CommonJS require / Node 특유의 ESM 관련 이슈 | localStorage, sessionStorage |
fetch (Node 18 이상) | 파일 시스템과 네트워크 소켓 접근 | 사용자 이벤트, 렌더링 |
setTimeout, setInterval | 자식 프로세스, 스트림 | History API, navigator |
최근 Node에는 fetch, URL, AbortController, structuredClone 같은 브라우저 API가 속속 들어와서 예전만큼 차이가 크진 않습니다. 그렇다고 해도 DOM이 Node로 들어올 일은 없고, 파일 시스템이 브라우저로 들어올 일도 없습니다.
Node vs Deno vs Bun
Node가 기본값이긴 하지만, 이제 유일한 자바스크립트 런타임은 아닙니다. Deno와 Bun이 대안으로 자리 잡고 있는데, Deno는 원래 Node를 만든 사람이 다시 만든 프로젝트이고, Bun은 속도에 집중한 신생 팀의 런타임입니다. 둘 다 자바스크립트(그리고 타입스크립트도 네이티브로) 를 실행하며, 테스트 러너나 번들러 같은 도구를 기본 내장하고 있습니다. 모듈 처리 방식, 권한 모델, 패키지 설치 방식 면에서 Node와 차이가 있습니다.
그래도 자바스크립트를 배우는 입장에서는 여전히 Node가 정답입니다. 공식 문서, 튜토리얼, 채용 공고가 모두 Node를 중심으로 돌아가기 때문이죠. 그리고 이벤트 루프, 모듈 시스템, 내장 API 같은 핵심 개념은 다른 런타임으로도 거의 그대로 옮겨갑니다. 일단 Node부터 익히고, 프로젝트에서 필요할 때 다른 런타임을 곁들이면 됩니다.
스크립트 빠르게 실행하기
작업하면서 실제로 코드를 돌려볼 수 있는 몇 가지 방법입니다.
# 파일 실행
node script.js
# 한 줄 코드 실행
node -e "console.log(2 ** 10)"
# REPL 열기 (대화형 프롬프트)
node
# 파일 감시하여 저장 시 재실행 (Node 18.11+)
node --watch script.js
REPL은 파일을 따로 만들지 않고 특정 메서드가 뭘 반환하는지 빠르게 확인하고 싶을 때 꽤 쓸모 있는 스크래치패드입니다. --watch는 개발할 때 특히 편한데, 저장만 하면 Node가 알아서 스크립트를 다시 실행해 줍니다.
다음 주제: 에러 처리
코드를 실행하는 것과, 뭔가 잘못됐을 때 제대로 대응하는 건 완전히 다른 얘기입니다. 파일 읽기가 실패하기도 하고, HTTP 요청이 타임아웃되기도 하고, JSON 파싱에서 예외가 터지기도 하죠. 다음 장에서는 try/catch, 에러 타입, 그리고 망가지는 상황에 대처하는 패턴들을 다룹니다 — Node 프로그램을 굴리다 보면 결국 모든 게 한 번씩은 터지거든요.
자주 묻는 질문
Node.js 런타임이란 무엇인가요?
Node.js는 브라우저 밖에서 자바스크립트를 실행할 수 있게 해주는 프로그램입니다. 크롬에서 JS를 돌리는 구글의 V8 엔진을 그대로 가져오고, 거기에 V8만으로는 할 수 없는 일들(파일 시스템 접근, 네트워킹, 프로세스 제어, 타이머 등)을 처리하는 C++ 레이어를 얹어 놓은 구조죠. 이 둘의 조합 덕분에 자바스크립트로 서버, CLI 도구, 빌드 도구까지 만들 수 있게 된 겁니다.
Node와 브라우저는 뭐가 다른가요?
둘 다 자바스크립트를 실행하지만, 언어를 감싸고 있는 API가 다릅니다. 브라우저에는 window, document, DOM이 있죠. 반면 Node에는 process, fs, http, __dirname, 그리고 CommonJS/ESM 모듈 시스템이 있습니다. Node에는 DOM이 없고, 브라우저에는 파일 시스템이 없습니다. 언어는 공유하지만 플랫폼은 완전히 다른 겁니다.
Node.js는 프레임워크인가요, 런타임인가요?
런타임입니다. Node 자체는 애플리케이션 구조를 전혀 강제하지 않아요. 그냥 자바스크립트를 실행하고 API를 제공할 뿐이죠. Express, Next.js, NestJS 같은 프레임워크는 모두 Node 위에 올라가 있는 것들입니다. Deno나 Bun은 Node와 같은 역할을 하는 대체 자바스크립트 런타임이고요.
Node의 이벤트 루프는 뭔가요?
이벤트 루프는 Node가 싱글 스레드 위에서도 여러 작업을 동시에 처리할 수 있게 해주는 핵심 구조입니다. 파일 읽기나 HTTP 요청, setTimeout 같은 비동기 작업을 호출하면, Node는 그 일을 시스템에 넘겨놓고 자기 할 일을 계속합니다. 작업이 끝나면 콜백이 큐에 쌓이고, 이벤트 루프가 순서대로 처리하죠. fs.readFile이 나머지 코드의 실행을 막지 않는 이유가 바로 이것 때문입니다.