BackendDevGuide0001
Node.js 완전 정복
비전공자도 현업에서 바로 쓸 수 있는 Node.js 완전 가이드
학습 목차
01. Node.js란 무엇인가?
핵심 한 줄 요약: Node.js는 브라우저 밖에서도 JavaScript를 실행할 수 있게 해주는 런타임 환경입니다.
피자 가게 비유로 이해하기
전통적인 서버(Apache 등)는 주문 하나당 직원 한 명을 배치합니다. 손님이 100명 오면 직원 100명이 필요하죠.
Node.js는 슈퍼 효율적인 혼자 일하는 요리사와 같습니다. 피자 반죽을 올려놓고(비동기 작업 시작), 기다리는 동안 다른 피자를 굽고(다른 요청 처리), 오븐 타이머 울리면 그때 마저 처리합니다.
이게 바로 이벤트 루프(Event Loop)와 논블로킹 I/O의 핵심입니다!
Node.js vs 기존 서버 비교표
| 비교 항목 | 기존 서버 (Apache/PHP) | Node.js |
|---|---|---|
| 처리 방식 | 멀티스레드 (동기) | 싱글스레드 + 이벤트 루프 (비동기) |
| 메모리 사용 | 요청마다 스레드 생성 | 이벤트 기반으로 적게 사용 |
| 동시 처리 | 스레드 수에 제한 | 수만 개 동시 처리 가능 |
| 언어 | PHP, Java, Python 등 | JavaScript (풀스택 가능!) |
| 속도 (I/O 많을 때) | 느림 | 매우 빠름 |
Node.js를 사용하는 유명 기업
Netflix (스트리밍), LinkedIn (모바일 백엔드), Uber (실시간 매칭), PayPal (결제 API), 카카오, 네이버, 쿠팡 등에서 활발히 사용 중입니다.
02. 설치와 개발환경 세팅
중요! 실무에서는 반드시 LTS(Long Term Support) 버전을 사용하세요. 짝수 버전이 LTS입니다 (예: 18.x, 20.x, 22.x)
nvm으로 설치하기 (강력 추천)
# macOS / Linux
$ curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.7/install.sh | bash
$ source ~/.bashrc
$ nvm install --lts
$ nvm use --lts
$ nvm ls
# 설치 확인
$ node -v # v22.14.0 같은 버전 출력
$ npm -v # npm 버전 출력
nvm을 쓰는 이유: 프로젝트마다 다른 Node.js 버전을 사용할 수 있습니다. 프로젝트 A는 Node 18, 프로젝트 B는 Node 20처럼 버전 전환이 자유롭습니다!
필수 개발 도구 설정
| 도구 | 용도 | 설치 |
|---|---|---|
| VS Code | 코드 편집기 (가장 추천) | code.visualstudio.com |
| ESLint | 코드 품질 검사 | npm install -D eslint |
| Prettier | 코드 자동 포매팅 | npm install -D prettier |
| Nodemon | 파일 변경 자동 재시작 | npm install -D nodemon |
| Postman | API 테스트 도구 | postman.com |
첫 번째 Node.js 프로그램
// app.js
const os = require("os");
const greeting = (name) => {
console.log(`안녕하세요, ${name}님! Node.js 세계에 오신걸 환영합니다!`);
console.log(`운영체제: ${os.platform()}, CPU 코어: ${os.cpus().length}개`);
console.log(`총 메모리: ${Math.round(os.totalmem() / 1024 / 1024 / 1024)}GB`);
};
greeting("개발자");
03. 모듈 시스템 완전 이해
| 구분 | CommonJS (CJS) | ES Modules (ESM) |
|---|---|---|
| 불러오기 | require() |
import |
| 내보내기 | module.exports |
export / export default |
| 로딩 시점 | 동기 (런타임) | 비동기 (파싱 타임) |
| 현업 사용 | 레거시, 구형 라이브러리 | 최신 코드 권장 |
CommonJS 실전 코드
// math.js - 모듈 정의
const add = (a, b) => a + b;
const multiply = (a, b) => a * b;
const PI = 3.14159;
module.exports = { add, multiply, PI };
// app.js - 모듈 사용
const { add, PI } = require("./math");
console.log(add(3, 4)); // 7
console.log(PI); // 3.14159
04. 비동기 프로그래밍 완전 마스터
Node.js의 핵심 경쟁력! Callback - Promise - async/await 순서로 진화했습니다. 현업에서는 async/await을 주로 사용합니다.
Callback (콜백) - 기초
const fs = require("fs");
fs.readFile("data.txt", "utf8", (err, data) => {
if (err) { console.error("에러:", err); return; }
console.log(data);
});
async/await - 현업 표준
const getUserData = async (userId) => {
try {
const user = await fetchUser(userId);
const posts = await fetchPosts(user.id);
console.log({ user, posts });
} catch (error) {
console.error("데이터 로드 실패:", error.message);
}
};
// 병렬 처리 (성능 최적화 핵심!)
const [user, settings] = await Promise.all([
fetchUser(userId),
fetchSettings(userId)
]);
05. 핵심 내장 모듈 정복
fs (File System) - 파일 읽기/쓰기
const fs = require("fs").promises; // Promise 버전 추천
const fileOperations = async () => {
await fs.writeFile("hello.txt", "안녕하세요!", "utf8");
const data = await fs.readFile("hello.txt", "utf8");
await fs.mkdir("logs", { recursive: true });
const files = await fs.readdir("./");
await fs.unlink("hello.txt");
};
http 모듈 - 기본 서버 구축
const http = require("http");
const server = http.createServer((req, res) => {
res.setHeader("Content-Type", "application/json; charset=utf-8");
if (req.url === "/" && req.method === "GET") {
res.writeHead(200);
res.end(JSON.stringify({ message: "Hello Node.js!" }));
} else {
res.writeHead(404);
res.end(JSON.stringify({ error: "Not Found" }));
}
});
server.listen(3000, () => console.log("서버: http://localhost:3000"));
06. npm & 패키지 관리 완전 정복
필수 npm 명령어
$ npm init -y # 프로젝트 초기화
$ npm install express # 운영 의존성 설치
$ npm install -D nodemon # 개발 의존성 설치
$ npm install # package.json 기반 모두 설치
$ npm uninstall express # 패키지 제거
$ npm run dev # scripts의 dev 실행
$ npm audit # 보안 취약점 검사
현업 필수 패키지 TOP 15
| 패키지명 | 용도 | 중요도 |
|---|---|---|
| express | 웹 서버 프레임워크 | 필수 |
| dotenv | 환경변수 관리 | 필수 |
| jsonwebtoken | JWT 인증 | 필수 |
| bcrypt | 비밀번호 해시 | 필수 |
| cors | CORS 설정 | 필수 |
| mongoose | MongoDB ORM | 중요 |
| joi / zod | 데이터 유효성 검사 | 필수 |
| winston | 로그 관리 | 중요 |
| axios | HTTP 클라이언트 | 중요 |
| multer | 파일 업로드 | 필요시 |
| socket.io | 실시간 통신 | 필요시 |
| jest / vitest | 테스트 프레임워크 | 필수 |
| helmet | 보안 헤더 설정 | 필수 |
| ioredis | Redis 캐싱 | 중요 |
| sequelize | SQL ORM | 중요 |
07. 이벤트 루프 완전 이해
면접 단골 질문! 이벤트 루프는 Node.js가 싱글 스레드임에도 비동기 작업을 처리할 수 있는 핵심 메커니즘입니다.
이벤트 루프 구성 요소
Call Stack (주방): 현재 실행 중인 코드
Web APIs / libuv: setTimeout, HTTP 요청, 파일 I/O 등 백그라운드 처리
Callback Queue (대기줄): 완료된 작업들이 실행되길 기다리는 곳
Microtask Queue (VIP 대기줄): Promise.then() - 일반 콜백보다 먼저 처리!
Event Loop: Call Stack이 비면 Queue에서 꺼내서 실행
실행 순서 예측 - 핵심 테스트
console.log("1. 시작"); // 동기
setTimeout(() => console.log("4. setTimeout 0ms"), 0);
Promise.resolve().then(() => console.log("3. Promise"));
console.log("2. 끝"); // 동기
// 출력: 1.시작 - 2.끝 - 3.Promise - 4.setTimeout (Micro Task 우선!)
| 단계 | 처리 내용 | 예시 |
|---|---|---|
| timers | setTimeout, setInterval 콜백 | setTimeout(fn, 100) |
| poll | I/O 이벤트, 새 연결 수락 | fs.readFile, HTTP 요청 |
| check | setImmediate 콜백 | setImmediate(fn) |
08. 스트림과 버퍼
대용량 데이터 처리의 핵심! 1GB 파일을 통째로 메모리에 올리면 서버가 다운됩니다. 스트림을 사용하면 조금씩 나눠서 처리하므로 메모리를 효율적으로 사용합니다.
// BAD: 통째로 읽기 (1GB 파일이면 1GB 메모리 사용)
fs.readFile("bigfile.mp4", (err, data) => fs.writeFile("copy.mp4", data, () => {}));
// GOOD: 스트림으로 처리 (메모리 효율적)
const fs = require("fs");
const readStream = fs.createReadStream("bigfile.mp4");
const writeStream = fs.createWriteStream("copy.mp4");
readStream.pipe(writeStream); // 조금씩 읽어서 쓰기
writeStream.on("finish", () => console.log("복사 완료!"));
09. 에러 처리 패턴 (현업 표준)
현업에서 에러 처리는 필수! 에러를 제대로 처리하지 않으면 서버가 갑자기 다운됩니다.
커스텀 에러 클래스 만들기
// errors/AppError.js
class AppError extends Error {
constructor(message, statusCode = 500, code = "INTERNAL_ERROR") {
super(message);
this.statusCode = statusCode;
this.code = code;
this.isOperational = true;
}
}
class NotFoundError extends AppError {
constructor(resource = "Resource") {
super(`${resource}을 찾을 수 없습니다`, 404, "NOT_FOUND");
}
}
// catchAsync 래퍼 - try-catch 없이 에러 자동 처리!
const catchAsync = (fn) => (req, res, next) => {
Promise.resolve(fn(req, res, next)).catch(next);
};
// 사용 예시
app.get("/users/:id", catchAsync(async (req, res) => {
const user = await User.findById(req.params.id);
if (!user) throw new NotFoundError("사용자");
res.json({ success: true, data: user });
}));
10. 실전 미니 프로젝트 - REST API 서버
무엇을 만들까? Express + Node.js로 완전한 Todo API 서버를 만들어봅니다. 이것을 이해하면 어떤 REST API도 만들 수 있습니다!
프로젝트 구조
todo-api/
├── src/
│ ├── routes/todo.routes.js
│ ├── controllers/todo.controller.js
│ ├── middleware/errorHandler.js
│ └── app.js
├── .env
└── package.json
src/app.js - 메인 앱 설정
require("dotenv").config();
const express = require("express");
const cors = require("cors");
const helmet = require("helmet");
const app = express();
app.use(helmet()); // 보안 헤더
app.use(cors()); // CORS 허용
app.use(express.json()); // JSON 파싱
app.use("/api/todos", todoRoutes);
app.use(errorHandler); // 에러 핸들러 (마지막!)
app.listen(3000, () => console.log("서버: http://localhost:3000"));
Todo CRUD API 테스트
# 목록 조회
$ curl http://localhost:3000/api/todos
# Todo 생성
$ curl -X POST http://localhost:3000/api/todos -H "Content-Type: application/json" -d '{"title":"Node.js 공부"}'
# 완료 처리
$ curl -X PATCH http://localhost:3000/api/todos/UUID -H "Content-Type: application/json" -d '{"completed":true}'
11. 현업/면접 단골 Q&A
Q1. Node.js가 싱글 스레드인데 어떻게 빠를 수 있나요?
A: Node.js는 I/O 작업(파일 읽기, DB 조회, HTTP 요청)을 논블로킹 방식으로 처리합니다. 요청을 보내고 기다리는 대신, 다른 작업을 처리하다가 완료 신호가 오면 콜백을 실행합니다. 단, CPU를 많이 사용하는 작업(암호화, 이미지 처리)에는 worker_threads를 사용해야 합니다.
Q2. Promise와 async/await의 차이가 무엇인가요?
A: async/await은 Promise의 문법적 설탕(Syntactic Sugar)입니다. 비동기 코드를 동기 코드처럼 읽히게 해서 가독성이 좋습니다. 현업에서는 async/await을 주로 쓰고, 병렬 처리가 필요할 때는 Promise.all()과 함께 사용합니다.
Q3. 이벤트 루프에서 Microtask와 Macrotask의 차이는?
A: Microtask는 Promise.then() 등이며 현재 태스크가 끝난 직후 즉시 실행됩니다. Macrotask는 setTimeout, I/O 등이며 이벤트 루프의 다음 순회에서 실행됩니다. 실행 순서: 동기 코드 - Microtask 큐 - Macrotask 큐 순서입니다.
Q4. require()와 import의 차이점은?
A: require()(CJS)는 동기적으로 런타임에 로드되며 조건문 안에서도 사용 가능합니다. import(ESM)는 정적 분석이 되어 Tree-shaking이 가능합니다. 최신 Node.js 프로젝트는 ESM을 권장합니다.
Q5. 대용량 파일을 처리할 때 어떻게 하나요?
A: Stream을 사용합니다. fs.readFile()은 파일 전체를 메모리에 올리므로 대용량에는 부적합합니다. fs.createReadStream()으로 청크 단위로 읽어 처리하고 pipe()로 연결합니다. 수 GB 파일도 일정한 메모리로 처리 가능합니다.
12. 백엔드 개발자 학습 로드맵
| 단계 | 학습 내용 | 예상 기간 | 상태 |
|---|---|---|---|
| 1단계 | JavaScript 기초 (ES6+) | 2~3주 | 완료 (Guide0000) |
| 2단계 | Node.js 기초 + 비동기 | 2~3주 | 현재 (Guide0001) |
| 3단계 | Express.js + REST API 설계 | 3~4주 | Guide0002 예정 |
| 4단계 | 데이터베이스 (MongoDB + PostgreSQL) | 3~4주 | Guide0003 예정 |
| 5단계 | 인증/보안 (JWT, OAuth, HTTPS) | 2~3주 | Guide0004 예정 |
| 6단계 | 테스트 코드 (Jest, Supertest) | 2~3주 | Guide0005 예정 |
| 7단계 | 배포 (Docker, AWS, CI/CD) | 3~4주 | Guide0006 예정 |
수고하셨습니다!
Node.js의 핵심 개념부터 실전 프로젝트까지 완주했습니다. 이 내용을 완전히 이해했다면 백엔드 개발자로서의 첫 발을 내딛은 것입니다!
다음 단계: BackendDevGuide0002
Express.js 완전 정복 - 미들웨어, 라우팅, MVC 패턴, REST API 설계