Node.js 시작하기 | 설치, 설정, Hello World
이 글의 핵심
Node.js 시작하기에 대한 실전 가이드입니다. 설치, 설정, Hello World 등을 예제와 함께 상세히 설명합니다.
들어가며
Node.js란?
Node.js는 Chrome V8 JavaScript 엔진으로 빌드된 JavaScript 런타임 환경입니다. 브라우저 밖에서 JavaScript를 실행할 수 있게 해줍니다.
이벤트 루프를 한 가지로 비유하면, 식당에서 주문을 받는 직원 한 명이 테이블마다 “음식 나오면 알려 드릴게요”라고 번호를 남기고 다음 손님을 받는 것과 비슷합니다. 파일·네트워크처럼 시간이 걸리는 일은 주방(운영체제·스레드 풀)에 맡겨 두고, 끝나면 등록해 둔 콜백을 순서에 맞춰 처리합니다. 우체국에서 소포를 부치고 번호표를 받아 나중에 찾으러 오는 흐름으로 이해하셔도 됩니다.
주요 특징:
- ✅ 비동기 I/O(한 작업이 끝나기를 기다리지 않고 다음 일을 넘기는 입출력): Non-blocking I/O로 높은 성능
- ✅ 단일 스레드: 이벤트 루프 기반
- ✅ 크로스 플랫폼: Windows, macOS, Linux 지원
- ✅ npm: 세계 최대 패키지 저장소
- ✅ JavaScript: 프론트엔드와 같은 언어 사용
Node.js가 적합한 경우:
- ✅ REST API 서버
- ✅ 실시간 애플리케이션 (채팅, 게임)
- ✅ 마이크로서비스
- ✅ CLI 도구
- ✅ 스트리밍 서비스
Node.js가 부적합한 경우:
- ❌ CPU 집약적 작업 (이미지/비디오 처리)
- ❌ 복잡한 계산 작업
1. Node.js vs 브라우저 JavaScript
비교
| 특징 | 브라우저 JavaScript | Node.js |
|---|---|---|
| 실행 환경 | 브라우저 (Chrome, Firefox) | 서버, 로컬 머신 |
| 전역 객체 | window | global |
| DOM 접근 | ✅ | ❌ |
| 파일 시스템 | ❌ | ✅ (fs 모듈) |
| HTTP 서버 | ❌ | ✅ (http 모듈) |
| 모듈 시스템 | ES Modules | CommonJS + ES Modules |
| 패키지 관리 | 없음 | npm, yarn |
공통 기능
브라우저와 Node.js 모두에서 사용할 수 있는 JavaScript 표준 기능들입니다:
// 콘솔 출력 - 디버깅과 로깅에 사용
console.log("Hello");
// 타이머 함수 - 일정 시간 후 코드 실행
setTimeout(() => {
console.log("1초 후 실행");
}, 1000);
// 반복 타이머 - 일정 간격으로 코드 반복 실행
setInterval(() => {
console.log("1초마다 실행");
}, 1000);
// 비동기 처리 - Promise와 async/await 문법
const fetchData = async () => {
const result = await Promise.resolve("데이터");
return result;
};
// JSON 파싱 - 문자열을 객체로, 객체를 문자열로 변환
const obj = JSON.parse('{"name": "홍길동"}');
const str = JSON.stringify({ name: "홍길동" });
브라우저와 Node.js 모두 같은 ECMAScript 문법으로 Promise와 async/await를 씁니다. JSON.parse와 JSON.stringify는 API 응답·.env를 다룰 때 문자열과 객체를 오갈 때마다 사용합니다.
Node.js 전용 기능
Node.js에서만 사용할 수 있는 서버 사이드 기능들입니다:
// 파일 시스템 - 파일 읽기/쓰기 기능
const fs = require('fs');
// readFileSync: 파일을 동기적으로 읽음 (파일을 다 읽을 때까지 대기)
// 'utf8': 텍스트 인코딩 지정 (한글 등 문자 처리)
const content = fs.readFileSync('file.txt', 'utf8');
// HTTP 서버 - 웹 서버 생성 기능
const http = require('http');
// createServer: 서버 인스턴스 생성
// req: 클라이언트 요청 객체, res: 서버 응답 객체
const server = http.createServer((req, res) => {
res.end('Hello'); // 응답 전송 후 연결 종료
});
// 경로 처리 - 파일 경로를 안전하게 조작
const path = require('path');
// __dirname: 현재 스크립트가 있는 디렉토리의 절대 경로
// join: 경로를 OS에 맞게 결합 (Windows: \, Linux: /)
const filePath = path.join(__dirname, 'file.txt');
// 프로세스 정보 - 현재 실행 중인 Node.js 프로세스 정보
console.log(process.version); // Node.js 버전 (예: v20.11.0)
console.log(process.platform); // 운영체제 (예: win32, darwin, linux)
console.log(process.cwd()); // 현재 작업 디렉토리
console.log(process.pid); // 프로세스 ID
fs는 비동기(readFile)와 동기(readFileSync)를 둘 다 제공합니다. 요청을 처리하는 콜백 안에서 readFileSync를 쓰면 그동안 다른 HTTP 요청도 기다리게 되므로, 서버 코드에서는 비동기 API가 기본입니다. path.join은 Windows의 \와 POSIX의 /를 섞지 않도록 경로를 이어 줍니다.
2. Node.js 설치
Windows
- nodejs.org 방문
- LTS 버전 다운로드 (안정 버전, 권장)
- 설치 프로그램 실행
- 설치 확인:
node --version
# v20.11.0
npm --version
# 10.2.4
macOS
방법 1: 공식 설치 프로그램
# nodejs.org에서 다운로드
방법 2: Homebrew
brew install node
# 버전 확인
node --version
npm --version
Linux (Ubuntu/Debian)
# NodeSource 저장소 추가
curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash -
# 설치
sudo apt-get install -y nodejs
# 버전 확인
node --version
npm --version
버전 관리 (nvm)
여러 Node.js 버전을 관리하려면 nvm 사용:
# nvm 설치 (Linux/macOS)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.0/install.sh | bash
# Node.js 설치
nvm install 20
nvm install 18
# 버전 전환
nvm use 20
nvm use 18
# 현재 버전
nvm current
# 설치된 버전 목록
nvm list
3. 첫 Node.js 프로그램
Hello World
// hello.js
console.log("Hello, Node.js!");
// 현재 Node.js 버전
console.log(`Node.js 버전: ${process.version}`);
// 플랫폼 정보
console.log(`플랫폼: ${process.platform}`);
실행:
node hello.js
# Hello, Node.js!
# Node.js 버전: v20.11.0
# 플랫폼: win32
명령줄 인자
// args.js
console.log("명령줄 인자:", process.argv);
// process.argv[0]: node 실행 파일 경로
// process.argv[1]: 스크립트 파일 경로
// process.argv[2~]: 사용자 인자
const args = process.argv.slice(2);
console.log("사용자 인자:", args);
if (args.length === 0) {
console.log("사용법: node args.js <이름>");
process.exit(1);
}
const name = args[0];
console.log(`안녕하세요, ${name}님!`);
실행:
node args.js 홍길동
# 안녕하세요, 홍길동님!
환경 변수
// env.js
console.log("환경 변수:", process.env);
// 특정 환경 변수
const port = process.env.PORT || 3000;
const nodeEnv = process.env.NODE_ENV || 'development';
console.log(`포트: ${port}`);
console.log(`환경: ${nodeEnv}`);
실행:
# Windows
set PORT=8080 && node env.js
# Linux/macOS
PORT=8080 node env.js
4. 첫 HTTP 서버
기본 서버
가장 간단한 형태의 HTTP 서버를 작성해 봅니다.
// server.js
const http = require('http');
// createServer: 서버 인스턴스 생성
// 콜백 함수는 요청이 올 때마다 실행됨
const server = http.createServer((req, res) => {
// req.method: HTTP 메서드 (GET, POST 등)
// req.url: 요청된 URL 경로 (예: /, /about)
console.log(`${req.method} ${req.url}`);
// writeHead: HTTP 상태 코드와 헤더 설정
// 200: 성공 응답
// Content-Type: 응답 데이터 형식 지정
// charset=utf-8: 한글 등 유니코드 문자 처리
res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' });
// end: 응답 본문을 전송하고 연결 종료
res.end('안녕하세요, Node.js 서버입니다!');
});
// 서버를 특정 포트에서 실행
const PORT = 3000;
server.listen(PORT, () => {
// 서버가 시작되면 이 콜백이 실행됨
console.log(`서버가 http://localhost:${PORT} 에서 실행 중`);
});
코드 흐름:
http모듈을 불러옵니다.createServer에 요청이 올 때마다 호출될 함수를 넘깁니다(콜백).listen으로 포트 3000에서 연결을 받을 준비를 합니다.- 브라우저 등이 접속하면
req·res가 채워진 채로 위 콜백이 실행됩니다. writeHead·end로 상태 코드·본문을 보내고 응답을 마칩니다.
실행:
node server.js
# 서버가 http://localhost:3000 에서 실행 중
브라우저에서 http://localhost:3000 접속하면 메시지가 표시됩니다.
라우팅 추가
URL 경로에 따라 다른 응답을 반환하는 라우팅 기능을 구현합니다:
// server-routing.js
const http = require('http');
const server = http.createServer((req, res) => {
// 구조 분해로 요청 메서드와 URL 추출
const { method, url } = req;
// 모든 응답에 공통으로 적용할 헤더 설정
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
// URL과 메서드에 따라 분기 처리
if (url === '/' && method === 'GET') {
// 루트 경로 - 홈 페이지
res.writeHead(200); // 200: OK 상태 코드
res.end('홈 페이지');
} else if (url === '/about' && method === 'GET') {
// /about 경로 - 소개 페이지
res.writeHead(200);
res.end('소개 페이지');
} else if (url === '/api/users' && method === 'GET') {
// API 엔드포인트 - JSON 응답
res.writeHead(200, { 'Content-Type': 'application/json' });
// JavaScript 객체를 JSON 문자열로 변환하여 응답
res.end(JSON.stringify({ users: ['홍길동', '김철수'] }));
} else {
// 매칭되는 경로가 없으면 404 에러
res.writeHead(404); // 404: Not Found 상태 코드
res.end('페이지를 찾을 수 없습니다');
}
});
server.listen(3000, () => {
console.log('서버 실행 중: http://localhost:3000');
});
라우팅 동작 원리:
- 클라이언트가 특정 URL로 요청
req.url과req.method를 확인- 조건문으로 매칭되는 경로 찾기
- 해당 경로에 맞는 응답 반환
- 매칭되는 경로가 없으면 404 에러
테스트:
# 브라우저 또는 curl로 테스트
curl http://localhost:3000/
curl http://localhost:3000/about
curl http://localhost:3000/api/users
HTML 응답
// server-html.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
const html = `
<!DOCTYPE html>
<html>
<head>
<title>Node.js 서버</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 50px auto;
padding: 20px;
}
h1 { color: #68a063; }
</style>
</head>
<body>
<h1>Node.js 서버에 오신 것을 환영합니다!</h1>
<p>현재 시간: ${new Date().toLocaleString('ko-KR')}</p>
<p>요청 URL: ${req.url}</p>
<p>요청 메서드: ${req.method}</p>
</body>
</html>
`;
res.end(html);
});
server.listen(3000, () => {
console.log('서버 실행 중: http://localhost:3000');
});
5. npm (Node Package Manager)
package.json 생성
# 프로젝트 폴더 생성
mkdir my-node-project
cd my-node-project
# package.json 생성 (대화형)
npm init
# package.json 생성 (기본값 사용)
npm init -y
생성된 package.json:
{
"name": "my-node-project",
"version": "1.0.0",
"description": "Node.js 프로젝트",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC"
}
패키지 설치
# 프로젝트 의존성 설치
npm install express
# 여러 패키지 동시 설치
npm install express body-parser cors
# 개발 의존성 설치 (배포 시 제외)
npm install --save-dev nodemon eslint
# 전역 설치
npm install -g nodemon
# 특정 버전 설치
npm install [email protected]
package.json 업데이트:
{
"dependencies": {
"express": "^4.18.2"
},
"devDependencies": {
"nodemon": "^3.0.1"
}
}
패키지 관리
# 설치된 패키지 목록
npm list
# 패키지 업데이트
npm update
# 패키지 제거
npm uninstall express
# 보안 취약점 검사
npm audit
# 취약점 자동 수정
npm audit fix
# 캐시 정리
npm cache clean --force
package.json scripts
{
"scripts": {
"start": "node index.js",
"dev": "nodemon index.js",
"test": "jest",
"lint": "eslint .",
"build": "webpack"
}
}
실행:
npm start # node index.js
npm run dev # nodemon index.js
npm test # jest
6. 모듈 시스템
CommonJS (기본)
Node.js의 기본 모듈 시스템입니다. 코드를 재사용 가능한 모듈로 분리할 수 있습니다.
내보내기 (module.exports):
// math.js - 수학 관련 함수들을 모듈로 만들기
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
const PI = 3.14159;
// 방법 1: 객체로 내보내기 (권장)
// module.exports에 객체를 할당하면 해당 객체가 모듈의 공개 API가 됨
module.exports = {
add, // add: add와 동일 (ES6 단축 속성)
subtract,
PI
};
// 방법 2: 개별 내보내기
// exports 객체에 속성을 추가하는 방식
// 주의: exports = {}는 작동하지 않음 (참조가 끊김)
exports.add = add;
exports.subtract = subtract;
exports.PI = PI;
가져오기 (require):
// app.js - math 모듈 사용하기
// require: 모듈을 불러와서 module.exports 객체를 반환
// './math': 상대 경로 (./ = 현재 디렉토리)
const math = require('./math');
// 모듈의 함수와 변수 사용
console.log(math.add(10, 5)); // 15
console.log(math.subtract(10, 5)); // 5
console.log(math.PI); // 3.14159
// 구조 분해 할당 - 필요한 것만 추출
// 코드가 더 간결해지고 가독성이 향상됨
const { add, subtract } = require('./math');
console.log(add(10, 5)); // 15 - math. 접두사 없이 바로 사용
모듈 캐싱:
require는 모듈을 처음 불러올 때만 실행하고, 이후에는 캐시된 결과를 반환- 같은 모듈을 여러 번
require해도 한 번만 실행됨 - 모듈은 싱글톤 패턴처럼 동작
ES Modules (최신)
package.json 설정:
{
"type": "module"
}
내보내기 (export):
// math.mjs (또는 .js with "type": "module")
export function add(a, b) {
return a + b;
}
export function subtract(a, b) {
return a - b;
}
export const PI = 3.14159;
// 기본 내보내기
export default function multiply(a, b) {
return a * b;
}
가져오기 (import):
// app.mjs
import multiply, { add, subtract, PI } from './math.mjs';
console.log(add(10, 5)); // 15
console.log(multiply(10, 5)); // 50
console.log(PI); // 3.14159
// 모두 가져오기
import * as math from './math.mjs';
console.log(math.add(10, 5));
내장 모듈
// 파일 시스템
const fs = require('fs');
// 경로 처리
const path = require('path');
// HTTP
const http = require('http');
// URL 처리
const url = require('url');
// 운영체제 정보
const os = require('os');
// 이벤트
const EventEmitter = require('events');
7. 파일 시스템 (fs)
동기 vs 비동기
Node.js의 핵심인 동기·비동기 처리 방식을 짚어 봅니다.
동기 (Sync): 작업이 끝날 때까지 대기
const fs = require('fs');
try {
// readFileSync: 파일을 다 읽을 때까지 다음 코드 실행 안 됨 (블로킹)
// 파일이 크면 프로그램이 멈춘 것처럼 보임
const data = fs.readFileSync('file.txt', 'utf8');
console.log(data); // 파일 내용 출력
} catch (err) {
// 파일이 없거나 권한이 없으면 에러 발생
console.error('에러:', err.message);
}
// 파일 읽기가 완전히 끝난 후에 실행됨
console.log('파일 읽기 완료');
동기 방식의 문제점:
- 파일 읽기가 끝날 때까지 다른 작업을 할 수 없음
- 서버에서 사용하면 다른 요청을 처리할 수 없어 성능 저하
- 초기화 코드나 간단한 스크립트에만 사용 권장
비동기 (Callback): 작업을 백그라운드에서 실행
const fs = require('fs');
// readFile: 파일 읽기를 백그라운드에서 실행 (논블로킹)
// 파일 읽기가 완료되면 콜백 함수가 호출됨
fs.readFile('file.txt', 'utf8', (err, data) => {
// 콜백 함수: 작업 완료 후 실행되는 함수
// Node.js 관례: 첫 번째 인자는 항상 에러 객체
if (err) {
console.error('에러:', err.message);
return; // 에러가 있으면 여기서 종료
}
// 에러가 없으면 data에 파일 내용이 담김
console.log(data);
});
// 파일 읽기와 동시에 바로 실행됨 (대기하지 않음)
console.log('파일 읽기 시작됨');
// 출력 순서: "파일 읽기 시작됨" → 파일 내용
비동기 방식의 장점:
- 파일 읽기 중에도 다른 작업 가능
- 서버가 여러 요청을 동시에 처리 가능
- Node.js의 핵심 강점
비동기 (Promise): 최신 방식
// fs.promises: Promise 기반 파일 시스템 API
const fs = require('fs').promises;
async function readFileAsync() {
try {
// await: Promise가 완료될 때까지 대기 (하지만 다른 작업은 블로킹하지 않음)
// 코드는 동기처럼 보이지만 실제로는 비동기로 동작
const data = await fs.readFile('file.txt', 'utf8');
console.log(data);
} catch (err) {
// try-catch로 에러 처리 (콜백보다 직관적)
console.error('에러:', err.message);
}
}
// async 함수는 항상 Promise를 반환
readFileAsync();
Promise 방식의 장점:
- 콜백 지옥(callback hell) 방지
- 에러 처리가 더 직관적 (try-catch)
- 코드 가독성 향상
- 현대적인 JavaScript 스타일
파일 읽기/쓰기
const fs = require('fs').promises;
async function fileOperations() {
try {
// 파일 쓰기
await fs.writeFile('output.txt', 'Hello, Node.js!', 'utf8');
console.log('파일 쓰기 완료');
// 파일 읽기
const data = await fs.readFile('output.txt', 'utf8');
console.log('파일 내용:', data);
// 파일 추가
await fs.appendFile('output.txt', '\n추가 내용', 'utf8');
// 파일 존재 확인
const exists = await fs.access('output.txt')
.then(() => true)
.catch(() => false);
console.log('파일 존재:', exists);
// 파일 삭제
await fs.unlink('output.txt');
console.log('파일 삭제 완료');
} catch (err) {
console.error('에러:', err.message);
}
}
fileOperations();
8. 실전 예제
예제 1: 간단한 웹 서버
// simple-server.js
const http = require('http');
const fs = require('fs').promises;
const path = require('path');
const server = http.createServer(async (req, res) => {
console.log(`${req.method} ${req.url}`);
if (req.url === '/') {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(`
<html>
<head><title>Node.js 서버</title></head>
<body>
<h1>환영합니다!</h1>
<ul>
<li><a href="/about">소개</a></li>
<li><a href="/api/time">현재 시간 API</a></li>
</ul>
</body>
</html>
`);
} else if (req.url === '/about') {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('<h1>소개 페이지</h1><p>Node.js로 만든 서버입니다.</p>');
} else if (req.url === '/api/time') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
time: new Date().toISOString(),
timestamp: Date.now()
}));
} else {
res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' });
res.end('404 - 페이지를 찾을 수 없습니다');
}
});
const PORT = 3000;
server.listen(PORT, () => {
console.log(`서버 실행 중: http://localhost:${PORT}`);
console.log('종료하려면 Ctrl+C를 누르세요');
});
예제 2: 파일 서버
// file-server.js
const http = require('http');
const fs = require('fs').promises;
const path = require('path');
const MIME_TYPES = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg',
'.txt': 'text/plain'
};
const server = http.createServer(async (req, res) => {
try {
let filePath = '.' + req.url;
if (filePath === './') {
filePath = './index.html';
}
const ext = path.extname(filePath);
const contentType = MIME_TYPES[ext] || 'application/octet-stream';
const data = await fs.readFile(filePath);
res.writeHead(200, { 'Content-Type': contentType });
res.end(data);
console.log(`✓ ${req.url}`);
} catch (err) {
if (err.code === 'ENOENT') {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('404 - File Not Found');
} else {
res.writeHead(500);
res.end('500 - Internal Server Error');
}
console.error(`✗ ${req.url}: ${err.message}`);
}
});
server.listen(3000, () => {
console.log('파일 서버 실행 중: http://localhost:3000');
});
예제 3: CLI 도구
// cli-tool.js
const fs = require('fs').promises;
const path = require('path');
async function countFiles(directory) {
try {
const files = await fs.readdir(directory);
let fileCount = 0;
let dirCount = 0;
for (const file of files) {
const filePath = path.join(directory, file);
const stats = await fs.stat(filePath);
if (stats.isDirectory()) {
dirCount++;
} else {
fileCount++;
}
}
console.log(`\n디렉토리: ${directory}`);
console.log(`파일: ${fileCount}개`);
console.log(`폴더: ${dirCount}개`);
console.log(`총: ${fileCount + dirCount}개`);
} catch (err) {
console.error('에러:', err.message);
process.exit(1);
}
}
// 명령줄 인자로 디렉토리 받기
const directory = process.argv[2] || '.';
countFiles(directory);
실행:
node cli-tool.js
node cli-tool.js ./src
9. nodemon (자동 재시작)
설치
# 전역 설치
npm install -g nodemon
# 프로젝트 개발 의존성으로 설치
npm install --save-dev nodemon
사용
# node 대신 nodemon 사용
nodemon server.js
# 파일 변경 시 자동으로 재시작됨
package.json 설정
{
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js"
}
}
npm run dev
nodemon.json 설정
{
"watch": ["src"],
"ext": "js,json",
"ignore": ["node_modules", "test"],
"delay": 1000
}
10. 디버깅
console.log 디버깅
console.log('변수:', variable);
console.log('객체:', JSON.stringify(obj, null, 2));
console.error('에러:', error);
console.warn('경고:', warning);
console.table([{ name: '홍길동', age: 25 }]);
VS Code 디버거
.vscode/launch.json:
{
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/index.js"
}
]
}
사용법:
- 중단점(breakpoint) 설정
F5또는 디버그 시작- 변수 값 확인, 단계별 실행
Node.js 내장 디버거
node inspect server.js
# 디버거 명령어
# cont (c): 계속 실행
# next (n): 다음 줄
# step (s): 함수 안으로
# out (o): 함수 밖으로
# repl: REPL 모드
11. 환경 변수 관리
.env 파일
# .env 파일 설치
npm install dotenv
.env:
PORT=3000
NODE_ENV=development
DATABASE_URL=mongodb://localhost:27017/mydb
API_KEY=your-secret-key
사용:
// config.js
require('dotenv').config();
const config = {
port: process.env.PORT || 3000,
nodeEnv: process.env.NODE_ENV || 'development',
databaseUrl: process.env.DATABASE_URL,
apiKey: process.env.API_KEY
};
module.exports = config;
// server.js
const config = require('./config');
console.log(`포트: ${config.port}`);
console.log(`환경: ${config.nodeEnv}`);
.gitignore:
node_modules/
.env
12. 자주 발생하는 문제
문제 1: 포트 이미 사용 중
에러:
Error: listen EADDRINUSE: address already in use :::3000
해결:
# Windows
netstat -ano | findstr :3000
taskkill /PID <PID> /F
# Linux/macOS
lsof -i :3000
kill -9 <PID>
# 또는 다른 포트 사용
const PORT = process.env.PORT || 3001;
문제 2: 모듈을 찾을 수 없음
에러:
Error: Cannot find module 'express'
해결:
# node_modules 확인
ls node_modules
# 패키지 재설치
npm install
# 특정 패키지 설치
npm install express
문제 3: 경로 문제
// ❌ 잘못된 경로
const data = fs.readFileSync('file.txt');
// ✅ 절대 경로 사용
const path = require('path');
const filePath = path.join(__dirname, 'file.txt');
const data = fs.readFileSync(filePath, 'utf8');
문제 4: 비동기 처리 실수
// ❌ 비동기 결과를 기다리지 않음
fs.readFile('file.txt', 'utf8', (err, data) => {
console.log(data);
});
console.log('완료'); // 먼저 출력됨!
// ✅ async/await 사용
async function readFile() {
const data = await fs.promises.readFile('file.txt', 'utf8');
console.log(data);
console.log('완료');
}
13. 실전 팁
프로젝트 구조
my-node-project/
├── src/
│ ├── controllers/
│ ├── models/
│ ├── routes/
│ └── utils/
├── public/
├── tests/
├── .env
├── .gitignore
├── package.json
├── README.md
└── server.js
베스트 프랙티스
// ✅ 에러 처리
process.on('uncaughtException', (err) => {
console.error('예상치 못한 에러:', err);
process.exit(1);
});
process.on('unhandledRejection', (reason, promise) => {
console.error('처리되지 않은 Promise 거부:', reason);
});
// ✅ Graceful Shutdown
process.on('SIGTERM', () => {
console.log('SIGTERM 신호 받음. 서버 종료 중...');
server.close(() => {
console.log('서버 종료됨');
process.exit(0);
});
});
// ✅ 환경 변수 사용
const PORT = process.env.PORT || 3000;
const NODE_ENV = process.env.NODE_ENV || 'development';
// ✅ 로깅
const isDev = NODE_ENV === 'development';
if (isDev) {
console.log('개발 모드');
}
성능 최적화
// ✅ 스트림 사용 (대용량 파일)
const fs = require('fs');
const readStream = fs.createReadStream('large-file.txt');
const writeStream = fs.createWriteStream('output.txt');
readStream.pipe(writeStream);
// ✅ 클러스터링 (멀티 코어 활용)
const cluster = require('cluster');
const os = require('os');
if (cluster.isMaster) {
const numCPUs = os.cpus().length;
for (let i = 0; i < numCPUs; i++) {
cluster.fork();
}
} else {
// 워커 프로세스에서 서버 실행
http.createServer((req, res) => {
res.end('Hello');
}).listen(3000);
}
정리
핵심 요약
- Node.js: Chrome V8 기반 JavaScript 런타임
- 설치: nodejs.org에서 LTS 버전 다운로드
- 실행:
node filename.js - npm: 패키지 관리자,
npm install <package> - 모듈: CommonJS (
require) 또는 ES Modules (import) - 파일 시스템:
fs모듈, 동기/비동기 - HTTP 서버:
http.createServer() - 개발 도구: nodemon, VS Code 디버거
Node.js 장점
- JavaScript 통일: 프론트엔드와 백엔드 모두 JavaScript
- 비동기 I/O: 높은 동시 처리 성능
- npm 생태계: 수백만 개의 패키지
- 빠른 개발: 간결한 코드, 빠른 프로토타이핑
- 활발한 커뮤니티: 풍부한 학습 자료
다음 단계
- Node.js 모듈 시스템
- Node.js 비동기 프로그래밍
- Express.js 웹 프레임워크
추천 학습 자료
공식 문서:
튜토리얼:
책:
- “Node.js 디자인 패턴”
- “Node.js 교과서”
다른 언어와 비교
- JavaScript 시작하기 | 웹 개발의 필수 언어 완벽 입문
관련 글
- Node.js 시리즈 전체 보기
- Node.js 모듈 시스템 | CommonJS와 ES Modules 완벽 가이드
- Node.js 비동기 프로그래밍 | Callback, Promise, Async/Await
- Express.js 완벽 가이드 | Node.js 웹 프레임워크