본문으로 건너뛰기

💚 Node.js란?

📖 정의

Node.js는 Chrome V8 JavaScript 엔진으로 구축된 JavaScript 런타임입니다. 브라우저 밖에서 JavaScript를 실행할 수 있게 해주며, 비동기 이벤트 기반 아키텍처로 높은 성능과 확장성을 제공합니다. 주로 서버 개발에 사용되며, npm(Node Package Manager)을 통해 방대한 라이브러리 생태계를 제공합니다.

🎯 비유로 이해하기

레스토랑 주방

Node.js를 레스토랑 주방에 비유하면:

전통적인 서버 (동기 방식)
- 요리사 1명이 한 명씩 순서대로 처리
- A 손님 주문 완료 → B 손님 주문 시작
- 느리고 비효율적

Node.js (비동기 방식)
- 요리사 1명이 여러 주문 동시 처리
- A 손님 스테이크 굽는 동안
→ B 손님 샐러드 준비
→ C 손님 음료 따르기
- 빠르고 효율적!

주방장 = 이벤트 루프
주문서 = 콜백 함수

도서관 vs 배달 서비스

동기 (Synchronous)
도서관에서 책 빌리기
1. 사서에게 요청
2. 사서가 책 찾는 동안 기다림 (블로킹)
3. 책 받음
4. 다음 사람 차례

비동기 (Asynchronous - Node.js)
배달 음식 주문
1. 앱으로 주문
2. 주문 확인 (즉시)
3. 다른 일 하기 (논블로킹)
4. 음식 도착 알림
5. 음식 받기

⚙️ 작동 원리

1. 이벤트 루프

┌───────────────────────────┐
│ Call Stack │ 실행 중인 코드
└───────────┬───────────────┘

┌───────────▼───────────────┐
│ Event Loop │ 작업 조율자
└───────────┬───────────────┘

┌───────┴───────┐
│ │
┌───▼────┐ ┌────▼─────┐
│Microtask│ │Task Queue│
│Queue │ │(Macrotask)│
└─────────┘ └───────────┘
Promise setTimeout
setInterval
I/O

2. 비동기 실행 흐름

console.log('1. 시작');

setTimeout(() => {
console.log('2. Timeout');
}, 0);

Promise.resolve().then(() => {
console.log('3. Promise');
});

console.log('4. 끝');

// 출력 순서:
// 1. 시작
// 4. 끝
// 3. Promise (Microtask Queue - 우선순위 높음)
// 2. Timeout (Task Queue)

💡 실제 예시

기본 HTTP 서버

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

const server = http.createServer((req, res) => {
// 요청 처리
console.log(`${req.method} ${req.url}`);

// 응답
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('<h1>안녕하세요, Node.js!</h1>');
});

const PORT = 3000;
server.listen(PORT, () => {
console.log(`서버 실행 중: http://localhost:${PORT}`);
});

// 실행: node server.js

Express로 API 만들기

// app.js
const express = require('express');
const app = express();

// 미들웨어
app.use(express.json()); // JSON 파싱

// 라우트
app.get('/', (req, res) => {
res.send('홈페이지');
});

app.get('/api/users', (req, res) => {
const users = [
{ id: 1, name: '김철수' },
{ id: 2, name: '이영희' }
];
res.json(users);
});

app.post('/api/users', (req, res) => {
const newUser = req.body;
console.log('새 사용자:', newUser);
res.status(201).json({ message: '생성 완료', user: newUser });
});

app.get('/api/users/:id', (req, res) => {
const { id } = req.params;
res.json({ id, name: '사용자 정보' });
});

// 에러 핸들링
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).json({ error: '서버 에러' });
});

const PORT = 3000;
app.listen(PORT, () => {
console.log(`서버 실행: http://localhost:${PORT}`);
});

비동기 처리 (콜백 vs Promise vs async/await)

const fs = require('fs').promises;

// 1. 콜백 방식 (레거시)
fs.readFile('file.txt', 'utf8', (err, data) => {
if (err) {
console.error('에러:', err);
return;
}
console.log('내용:', data);
});

// 2. Promise 방식
fs.readFile('file.txt', 'utf8')
.then(data => {
console.log('내용:', data);
})
.catch(err => {
console.error('에러:', err);
});

// 3. async/await 방식 (권장)
async function readFileAsync() {
try {
const data = await fs.readFile('file.txt', 'utf8');
console.log('내용:', data);
} catch (err) {
console.error('에러:', err);
}
}

readFileAsync();

여러 파일 동시 읽기

const fs = require('fs').promises;

// ❌ 순차 실행 (느림)
async function readFilesSequential() {
const file1 = await fs.readFile('file1.txt', 'utf8');
const file2 = await fs.readFile('file2.txt', 'utf8');
const file3 = await fs.readFile('file3.txt', 'utf8');
// 총 시간: 300ms (각 100ms)
return [file1, file2, file3];
}

// ✅ 병렬 실행 (빠름)
async function readFilesParallel() {
const [file1, file2, file3] = await Promise.all([
fs.readFile('file1.txt', 'utf8'),
fs.readFile('file2.txt', 'utf8'),
fs.readFile('file3.txt', 'utf8')
]);
// 총 시간: 100ms (동시 실행)
return [file1, file2, file3];
}

데이터베이스 연동

const express = require('express');
const mysql = require('mysql2/promise');

const app = express();
app.use(express.json());

// 데이터베이스 연결 풀
const pool = mysql.createPool({
host: 'localhost',
user: 'root',
password: 'password',
database: 'mydb',
waitForConnections: true,
connectionLimit: 10
});

// 사용자 목록 조회
app.get('/api/users', async (req, res) => {
try {
const [rows] = await pool.execute('SELECT * FROM users');
res.json(rows);
} catch (error) {
console.error(error);
res.status(500).json({ error: '데이터베이스 에러' });
}
});

// 사용자 생성
app.post('/api/users', async (req, res) => {
const { name, email } = req.body;

try {
const [result] = await pool.execute(
'INSERT INTO users (name, email) VALUES (?, ?)',
[name, email]
);

res.status(201).json({
message: '생성 완료',
userId: result.insertId
});
} catch (error) {
console.error(error);
res.status(500).json({ error: '데이터베이스 에러' });
}
});

app.listen(3000);

파일 스트림 처리

const fs = require('fs');

// ❌ 전체 파일을 메모리에 로드 (큰 파일은 메모리 부족)
const data = fs.readFileSync('large-file.txt', 'utf8');
console.log(data);

// ✅ 스트림으로 조금씩 처리 (메모리 효율적)
const readStream = fs.createReadStream('large-file.txt', 'utf8');

readStream.on('data', chunk => {
console.log('청크:', chunk.length, '바이트');
});

readStream.on('end', () => {
console.log('읽기 완료');
});

readStream.on('error', err => {
console.error('에러:', err);
});

// 파일 복사 (스트림 + 파이프)
const readStream = fs.createReadStream('source.txt');
const writeStream = fs.createWriteStream('dest.txt');

readStream.pipe(writeStream);

writeStream.on('finish', () => {
console.log('복사 완료');
});

환경변수 관리

// .env 파일
/*
PORT=3000
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=secret
JWT_SECRET=my-secret-key
*/

// app.js
require('dotenv').config();

const express = require('express');
const app = express();

const PORT = process.env.PORT || 3000;
const DB_HOST = process.env.DB_HOST;

console.log('포트:', PORT);
console.log('DB 호스트:', DB_HOST);

app.listen(PORT);

// ⚠️ .env는 .gitignore에 추가!

npm 스크립트

// package.json
{
"name": "my-app",
"version": "1.0.0",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"test": "jest",
"build": "webpack --mode production",
"lint": "eslint ."
},
"dependencies": {
"express": "^4.18.0",
"dotenv": "^16.0.0"
},
"devDependencies": {
"nodemon": "^2.0.0",
"jest": "^29.0.0"
}
}
# 스크립트 실행
npm start # node server.js
npm run dev # nodemon server.js (자동 재시작)
npm test # jest
npm run build # webpack

🤔 자주 묻는 질문

Q1. Node.js는 왜 빠른가요?

A:

// 1. 비동기 I/O
// 블로킹 (전통적인 서버)
const data = readFileSync('file.txt'); // 파일 읽을 때까지 대기
processData(data);

// 논블로킹 (Node.js)
readFile('file.txt', (err, data) => {
processData(data);
});
// 파일 읽는 동안 다른 요청 처리 가능!

// 2. 이벤트 기반
// - 싱글 스레드로 수천 개 연결 처리
// - 컨텍스트 스위칭 비용 없음

// 3. V8 엔진
// - JIT 컴파일로 빠른 실행
// - 메모리 효율적

// 비교
Apache (스레드 기반): 요청당 스레드 생성
Node.js (이벤트 기반): 하나의 스레드로 모든 요청 처리

// 벤치마크 예시
Apache: 10,000 동시 연결 → 메모리 소진
Node.js: 100,000 동시 연결 → 원활 동작

Q2. Node.js의 단점은?

A:

// 1. CPU 집약적 작업에 부적합
// ❌ 나쁜 예: 무거운 계산
app.get('/heavy', (req, res) => {
let result = 0;
for (let i = 0; i < 10000000000; i++) {
result += i; // 이벤트 루프 차단!
}
res.json({ result });
});

// ✅ 해결: Worker Threads 사용
const { Worker } = require('worker_threads');

app.get('/heavy', (req, res) => {
const worker = new Worker('./heavy-worker.js');
worker.on('message', result => {
res.json({ result });
});
});

// 2. 콜백 지옥 (Callback Hell)
// ❌ 읽기 어려운 코드
getData(function(a) {
getMoreData(a, function(b) {
getMoreData(b, function(c) {
getMoreData(c, function(d) {
// ...
});
});
});
});

// ✅ async/await로 해결
const a = await getData();
const b = await getMoreData(a);
const c = await getMoreData(b);
const d = await getMoreData(c);

// 3. 에러 처리 주의
// Promise는 반드시 catch 필요
fetchData().catch(err => console.error(err));

// async 함수는 try-catch
async function main() {
try {
await fetchData();
} catch (err) {
console.error(err);
}
}

Q3. npm이란?

A:

# npm (Node Package Manager)
# - Node.js 패키지 관리자
# - 100만 개 이상의 패키지

# 패키지 설치
npm install express # 프로젝트에 설치
npm install -g nodemon # 전역 설치
npm install --save-dev jest # 개발 의존성

# 프로젝트 초기화
npm init # 대화형
npm init -y # 기본값으로 빠르게

# package.json 생성
{
"name": "my-app",
"version": "1.0.0",
"dependencies": {
"express": "^4.18.0" # 실행 시 필요
},
"devDependencies": {
"jest": "^29.0.0" # 개발 시에만 필요
}
}

# 버전 관리
^4.18.0 # 4.x.x (메이저 버전 고정)
~4.18.0 # 4.18.x (마이너 버전 고정)
4.18.0 # 정확히 4.18.0

# 유용한 명령어
npm list # 설치된 패키지 확인
npm outdated # 업데이트 가능한 패키지
npm update # 패키지 업데이트
npm uninstall # 패키지 삭제
npm audit # 보안 취약점 확인
npm audit fix # 자동 수정

# npx (패키지 실행)
npx create-react-app my-app # 설치 없이 실행
npx cowsay "Hello" # 일회성 실행

Q4. CommonJS vs ES Modules?

A:

// CommonJS (전통적인 방식)
// require/module.exports

// math.js
function add(a, b) {
return a + b;
}
module.exports = { add };

// app.js
const { add } = require('./math');
console.log(add(1, 2));

// ES Modules (현대적인 방식)
// import/export

// math.js
export function add(a, b) {
return a + b;
}

// app.js
import { add } from './math.js';
console.log(add(1, 2));

// package.json에서 활성화
{
"type": "module"
}

// 또는 .mjs 확장자 사용
// math.mjs
// app.mjs

Q5. Node.js는 언제 사용하나요?

A:

// ✅ Node.js가 좋은 경우
1. RESTful API 서버
- 많은 I/O 작업
- 실시간 데이터

2. 실시간 애플리케이션
- 채팅 앱
- 협업 도구
- 게임 서버

3. 마이크로서비스
- 가볍고 빠른 서비스

4. 서버리스 함수
- AWS Lambda
- Google Cloud Functions

5. 도구 및 빌드 시스템
- Webpack, Babel
- 테스트 프레임워크

// ❌ Node.js가 부적합한 경우
1. CPU 집약적 작업
- 이미지/비디오 처리
- 머신러닝
- 암호화폐 마이닝
Python, Go, Rust 등 사용

2. 복잡한 비즈니스 로직
- 대규모 엔터프라이즈
Java, C# 등 사용

🎓 다음 단계

Node.js를 이해했다면, 다음을 학습해보세요:

  1. React란? - 프론트엔드 연동
  2. 데이터베이스란? - 백엔드 완성
  3. Docker란? (문서 작성 예정) - 배포 환경 구성

실습해보기

# 1. Node.js 설치 확인
node --version
npm --version

# 2. 첫 프로젝트
mkdir my-app
cd my-app
npm init -y

# 3. Express 설치
npm install express

# 4. 서버 만들기 (server.js)
# (위의 Express 예시 참고)

# 5. 실행
node server.js

# 6. 자동 재시작 (개발 편의)
npm install --save-dev nodemon
npx nodemon server.js

추천 패키지

// 웹 프레임워크
express // 가장 인기 있는 프레임워크
fastify // 빠른 대안
koa // Express 개선 버전

// 데이터베이스
mysql2 // MySQL
pg // PostgreSQL
mongodb // MongoDB
redis // Redis

// 인증
passport // 인증 미들웨어
jsonwebtoken // JWT
bcrypt // 비밀번호 해싱

// 유틸리티
dotenv // 환경변수
lodash // 유틸 함수
moment // 날짜 처리
axios // HTTP 클라이언트

// 개발 도구
nodemon // 자동 재시작
pm2 // 프로세스 관리
eslint // 코드 품질

🎬 마무리

Node.js는 JavaScript로 서버를 만드는 강력한 플랫폼입니다:

  • 비동기 I/O: 높은 성능과 확장성
  • JavaScript: 프론트엔드와 같은 언어
  • npm: 방대한 패키지 생태계
  • 이벤트 루프: 효율적인 동시 처리

Node.js로 풀스택 개발자가 되어보세요! 💚✨