본문으로 건너뛰기
Advertisement

Node.js 개요

Node.js란 무엇인가?

Node.js는 브라우저 밖에서 JavaScript를 실행할 수 있게 해주는 런타임 환경입니다. 2009년 Ryan Dahl이 발표한 이후 서버 사이드 JavaScript의 표준으로 자리잡았습니다.

전통적인 웹 서버(Apache, Tomcat)는 요청 하나당 스레드 하나를 점유하는 방식이었습니다. 동시 접속자가 늘어나면 스레드도 선형적으로 늘어나고, 메모리와 CPU가 금방 고갈됩니다. Node.js는 이 문제를 이벤트 드리븐 논블로킹 I/O 모델로 해결했습니다.


V8 + libuv: Node.js의 두 엔진

┌─────────────────────────────────────┐
│ Node.js 런타임 │
│ │
│ ┌──────────┐ ┌───────────────┐ │
│ │ V8 │ │ libuv │ │
│ │ (JS 실행) │ │ (비동기 I/O) │ │
│ └──────────┘ └───────────────┘ │
│ │
│ ┌─────────────────────────────┐ │
│ │ Node.js Core API │ │
│ │ (fs, http, crypto, ...) │ │
│ └─────────────────────────────┘ │
└─────────────────────────────────────┘

V8 엔진

구글이 Chrome을 위해 개발한 고성능 JavaScript 엔진입니다. JIT(Just-In-Time) 컴파일로 JavaScript 코드를 기계어로 변환합니다. Node.js는 이 V8을 내장해 JavaScript를 서버에서 실행합니다.

libuv

C 언어로 작성된 크로스 플랫폼 비동기 I/O 라이브러리입니다. 이벤트 루프, 스레드 풀, 파일 시스템, 네트워킹을 담당합니다. Node.js의 논블로킹 특성은 대부분 libuv 덕분입니다.


이벤트 드리븐 논블로킹 I/O 모델

블로킹 vs 논블로킹

블로킹 방식은 파일을 읽는 동안 해당 스레드 전체가 멈춥니다:

// 블로킹 코드 (동기)
const fs = require('fs');

const data = fs.readFileSync('/large-file.txt', 'utf8'); // 파일 읽는 동안 멈춤
console.log('파일 읽기 완료:', data.length, '바이트');
console.log('이 코드는 위가 끝나야 실행됨');

논블로킹 방식은 I/O 작업을 백그라운드에 맡기고 즉시 다음 코드를 실행합니다:

// 논블로킹 코드 (비동기)
const fs = require('fs');

fs.readFile('/large-file.txt', 'utf8', (err, data) => {
if (err) throw err;
console.log('파일 읽기 완료:', data.length, '바이트');
});
console.log('이 코드가 먼저 실행됨!'); // 파일 읽기 전에 출력

이벤트 루프

Node.js의 핵심입니다. 단일 스레드에서 동시성을 처리하는 메커니즘입니다:

   ┌──────────────────────────────────┐
│ 이벤트 루프 │
│ │
│ ┌──────────┐ │
│ │ timers │ setTimeout, setInterval
│ └─────┬────┘ │
│ │ │
│ ┌─────▼────────────┐ │
│ │ pending callbacks│ I/O 에러 │
│ └─────┬────────────┘ │
│ │ │
│ ┌─────▼──────┐ │
│ │ poll │ 새 I/O 이벤트 대기 │
│ └─────┬──────┘ │
│ │ │
│ ┌─────▼──────┐ │
│ │ check │ setImmediate │
│ └─────┬──────┘ │
│ │ │
│ ┌─────▼─────────────┐ │
│ │ close callbacks │ │
│ └───────────────────┘ │
└──────────────────────────────────┘
// 이벤트 루프 동작 확인
console.log('1: 동기 코드');

setTimeout(() => console.log('3: setTimeout (0ms)'), 0);

Promise.resolve().then(() => console.log('2: Promise (마이크로태스크)'));

console.log('1-2: 또 동기 코드');

// 출력 순서:
// 1: 동기 코드
// 1-2: 또 동기 코드
// 2: Promise (마이크로태스크)
// 3: setTimeout (0ms)

npm 생태계

npm(Node Package Manager)은 세계 최대 오픈 소스 패키지 저장소입니다. 230만 개 이상의 패키지가 등록되어 있습니다.

package.json 기초

# 새 프로젝트 초기화
mkdir my-project && cd my-project
npm init -y

생성되는 package.json:

{
"name": "my-project",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"start": "node index.js",
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC"
}

패키지 설치

# 일반 의존성 설치
npm install express

# 개발 의존성 설치
npm install --save-dev nodemon

# 전역 설치
npm install -g pm2

# 설치 후 package.json 확인
cat package.json

node_modules 구조

my-project/
├── node_modules/
│ ├── express/
│ ├── body-parser/
│ └── ...
├── package.json
└── package-lock.json

.gitignore에 반드시 추가:

node_modules/

Node.js vs 브라우저 차이

항목Node.js브라우저
전역 객체globalwindow
DOM API없음document, navigator
파일 시스템fs 모듈로 접근 가능접근 불가 (보안)
모듈 시스템CommonJS(require) + ESMESM(import)
실행 환경서버/로컬 머신샌드박스 환경
프로세스 관리process 객체없음
// Node.js에서만 동작
const os = require('os');
console.log('플랫폼:', process.platform);
console.log('Node 버전:', process.version);
console.log('CPU 코어:', os.cpus().length);
console.log('메모리:', Math.round(os.totalmem() / 1024 / 1024 / 1024), 'GB');

// 브라우저에서는 존재하지 않는 전역 객체
console.log(typeof window); // undefined (Node.js)
console.log(typeof global); // object (Node.js)
console.log(typeof process); // object (Node.js)

모듈 시스템 차이

// Node.js CommonJS (기본)
const express = require('express');
module.exports = { myFunction };

// Node.js ESM (package.json에 "type": "module" 추가 필요)
import express from 'express';
export { myFunction };

Node.js 버전 관리

Node.js는 짝수 버전(LTS)과 홀수 버전(Current)으로 나뉩니다:

버전종류특징
20.xLTS (Active)장기 지원, 프로덕션 권장
22.xLTS (Maintenance)보안 패치만
23.xCurrent최신 기능, 실험적
24.x개발 중미래 버전

nvm으로 버전 관리

# nvm 설치 (macOS/Linux)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash

# 특정 버전 설치
nvm install 20
nvm install 22

# 버전 전환
nvm use 20

# 기본 버전 설정
nvm alias default 20

# 설치된 버전 목록
nvm ls

# 현재 버전 확인
node --version
npm --version

실전: 첫 Node.js 서버 실행

기본 HTTP 서버

// server.js
const http = require('http');

const hostname = '127.0.0.1';
const port = 3000;

const server = http.createServer((req, res) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
res.end('안녕하세요, Node.js 서버입니다!\n');
});

server.listen(port, hostname, () => {
console.log(`서버 실행 중: http://${hostname}:${port}/`);
});
node server.js
# 서버 실행 중: http://127.0.0.1:3000/

curl http://127.0.0.1:3000
# 안녕하세요, Node.js 서버입니다!

JSON API 서버

// api-server.js
const http = require('http');

const users = [
{ id: 1, name: '김철수', role: 'admin' },
{ id: 2, name: '이영희', role: 'user' },
{ id: 3, name: '박지민', role: 'user' },
];

const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/json; charset=utf-8');

if (req.url === '/users' && req.method === 'GET') {
res.statusCode = 200;
res.end(JSON.stringify({ success: true, data: users }));
} else if (req.url === '/health') {
res.statusCode = 200;
res.end(JSON.stringify({ status: 'ok', uptime: process.uptime() }));
} else {
res.statusCode = 404;
res.end(JSON.stringify({ success: false, message: '404 Not Found' }));
}
});

server.listen(3000, () => {
console.log('API 서버: http://localhost:3000');
console.log('엔드포인트: GET /users, GET /health');
});

REPL 활용

# Node.js REPL 실행
node

# REPL 내부에서
> const arr = [1, 2, 3, 4, 5]
> arr.filter(n => n % 2 === 0)
[ 2, 4 ]
> arr.reduce((acc, n) => acc + n, 0)
15
> .exit

Node.js 주요 활용 분야

언제 Node.js가 빛나는가?

  • 실시간 애플리케이션: 채팅, 알림, 게임 서버 — 이벤트 드리븐 모델의 강점
  • API 서버: RESTful API, GraphQL — 빠른 JSON 처리
  • 마이크로서비스: 경량화, 빠른 시작 시간
  • 스트리밍 서비스: 대용량 파일/미디어 스트리밍
  • CLI 도구: npm, webpack, ESLint 등 대부분의 JS 도구가 Node.js 기반

언제 다른 선택을 고려해야 하는가?

  • CPU 집약적 연산: 이미지/동영상 처리, 암호화, 머신러닝 — Python/Go가 더 적합
  • 멀티스레드 필요: 기본적으로 단일 스레드 (worker_threads로 해결 가능)
  • 메모리 집약적 작업: V8 힙 메모리 제한 (~1.5GB 기본)

고수 팁

process 객체 활용

// process 정보 출력
console.log('Node 버전:', process.version);
console.log('플랫폼:', process.platform); // win32, linux, darwin
console.log('아키텍처:', process.arch); // x64, arm64
console.log('PID:', process.pid);
console.log('작업 디렉토리:', process.cwd());
console.log('실행 경로:', process.execPath);

// 환경 변수
process.env.NODE_ENV = 'production';
console.log('환경:', process.env.NODE_ENV);

// 프로세스 종료
process.on('exit', (code) => {
console.log(`프로세스 종료 코드: ${code}`);
});

// 정상 종료
process.exit(0);
// 에러 종료
process.exit(1);

성능 측정

// 실행 시간 측정
const { performance } = require('perf_hooks');

const start = performance.now();

// 측정할 작업
let sum = 0;
for (let i = 0; i < 1_000_000; i++) {
sum += i;
}

const end = performance.now();
console.log(`실행 시간: ${(end - start).toFixed(2)}ms`);
console.log(`결과: ${sum}`);

// console.time 방법
console.time('loop');
let total = 0;
for (let i = 0; i < 1_000_000; i++) total += i;
console.timeEnd('loop');

메모리 사용량 모니터링

// 메모리 사용량 확인
const used = process.memoryUsage();
console.log('메모리 사용량:');
for (const [key, value] of Object.entries(used)) {
console.log(` ${key}: ${Math.round(value / 1024 / 1024 * 100) / 100} MB`);
}
// heapUsed: 실제 사용 중인 힙 메모리
// heapTotal: 전체 힙 크기
// rss: 운영체제가 할당한 전체 메모리
// external: C++ 바인딩 메모리
Advertisement