Node.js
Node JavaScript Runtime
V8 엔진 기반 서버사이드 JavaScript 런타임. 이벤트 기반 비동기 I/O로 고성능 웹 서버 구축.
Node JavaScript Runtime
V8 엔진 기반 서버사이드 JavaScript 런타임. 이벤트 기반 비동기 I/O로 고성능 웹 서버 구축.
Node.js는 Google Chrome의 V8 JavaScript 엔진을 기반으로 구축된 서버사이드 JavaScript 런타임 환경입니다. 기존에 웹 브라우저에서만 실행 가능했던 JavaScript를 서버에서도 실행할 수 있게 함으로써, 프론트엔드와 백엔드를 동일한 언어로 개발할 수 있는 풀스택 JavaScript 개발을 가능하게 했습니다. V8 엔진은 JavaScript 코드를 네이티브 머신 코드로 컴파일하여 뛰어난 실행 성능을 제공합니다.
Node.js는 2009년 Ryan Dahl에 의해 개발되었습니다. 당시 Apache 서버의 동시 연결 처리 한계와 블로킹 I/O 모델의 비효율성에 문제의식을 가진 Dahl은 비동기 이벤트 기반 아키텍처를 채택하여 이 문제를 해결했습니다. JSConf EU 2009에서 처음 공개된 이후 빠르게 성장하여, 현재는 OpenJS Foundation에서 관리되며 전 세계 수백만 개발자가 사용하는 핵심 백엔드 기술로 자리잡았습니다.
Node.js의 핵심 특징은 이벤트 루프(Event Loop)와 비동기 I/O 모델입니다. 단일 스레드에서 동작하지만, 이벤트 루프를 통해 I/O 작업을 논블로킹으로 처리합니다. 파일 읽기, 네트워크 요청 등의 I/O 작업이 완료될 때까지 기다리지 않고 다음 작업을 즉시 처리하며, I/O 완료 시 콜백 함수를 실행합니다. 이 아키텍처 덕분에 동시에 수만 개의 연결을 효율적으로 처리할 수 있어 실시간 애플리케이션에 적합합니다.
Node.js 생태계의 중심에는 npm(Node Package Manager)이 있습니다. npm은 세계 최대의 오픈소스 패키지 레지스트리로, 200만 개 이상의 패키지가 등록되어 있습니다. Express.js, NestJS 같은 웹 프레임워크부터 데이터베이스 드라이버, 유틸리티 라이브러리까지 다양한 패키지를 활용할 수 있어 개발 생산성이 크게 향상됩니다. Netflix, LinkedIn, Uber, PayPal 등 글로벌 기업들이 Node.js를 프로덕션 환경에서 활용하고 있습니다.
// Node.js 내장 http 모듈로 간단한 웹 서버 생성
const http = require('http');
// 서버 설정
const hostname = '127.0.0.1';
const port = 3000;
// HTTP 서버 생성 - 비동기 요청 처리
const server = http.createServer((req, res) => {
// 응답 헤더 설정
res.statusCode = 200;
res.setHeader('Content-Type', 'text/plain; charset=utf-8');
// URL에 따른 라우팅
if (req.url === '/') {
res.end('안녕하세요! Node.js 서버입니다.');
} else if (req.url === '/api') {
res.setHeader('Content-Type', 'application/json');
res.end(JSON.stringify({ message: 'API 응답', status: 'success' }));
} else {
res.statusCode = 404;
res.end('페이지를 찾을 수 없습니다.');
}
});
// 서버 시작
server.listen(port, hostname, () => {
console.log(`서버 실행 중: http://${hostname}:${port}/`);
});
// Express.js를 사용한 RESTful API 서버
const express = require('express');
const app = express();
const port = 3000;
// JSON 파싱 미들웨어
app.use(express.json());
// 샘플 데이터
let users = [
{ id: 1, name: '김철수', email: 'kim@example.com' },
{ id: 2, name: '이영희', email: 'lee@example.com' }
];
// GET - 모든 사용자 조회
app.get('/api/users', (req, res) => {
res.json(users);
});
// GET - 특정 사용자 조회
app.get('/api/users/:id', (req, res) => {
const user = users.find(u => u.id === parseInt(req.params.id));
if (!user) return res.status(404).json({ error: '사용자를 찾을 수 없습니다' });
res.json(user);
});
// POST - 새 사용자 생성
app.post('/api/users', (req, res) => {
const newUser = {
id: users.length + 1,
name: req.body.name,
email: req.body.email
};
users.push(newUser);
res.status(201).json(newUser);
});
// 서버 시작
app.listen(port, () => {
console.log(`Express 서버 실행 중: http://localhost:${port}`);
});
// async/await를 사용한 비동기 처리 (콜백 지옥 방지)
const fs = require('fs').promises;
// 파일 읽기 + API 호출 순차 처리
async function processData() {
try {
// 1. 설정 파일 읽기
const config = JSON.parse(
await fs.readFile('./config.json', 'utf-8')
);
console.log('설정 로드 완료:', config.apiUrl);
// 2. API 호출 (fetch는 Node 18+)
const response = await fetch(config.apiUrl);
const data = await response.json();
console.log('API 데이터 수신:', data.length, '건');
// 3. 결과 파일 저장
await fs.writeFile(
'./output.json',
JSON.stringify(data, null, 2)
);
console.log('파일 저장 완료');
return data;
} catch (error) {
// 에러 핸들링
console.error('처리 중 오류 발생:', error.message);
throw error;
}
}
// 실행
processData().then(() => console.log('모든 작업 완료'));
PM 박과장:
"이번 신규 프로젝트 API 서버를 어떤 기술로 구축할지 논의해봅시다. 실시간 알림 기능이 핵심인데요."
백엔드 김대리:
"Node.js를 추천합니다. 실시간 기능에 Socket.io 연동이 쉽고, 이벤트 기반 아키텍처라 동시 접속 처리에 강점이 있어요. 프론트엔드가 React이니 JavaScript로 통일하면 코드 공유도 가능합니다."
시니어 개발자 이차장:
"좋은 선택이에요. 다만 CPU 집약적 작업이 있다면 Worker Threads를 활용하거나, 해당 부분만 별도 마이크로서비스로 분리하는 것도 고려해야 합니다. Express.js로 시작하고, 규모가 커지면 NestJS로 마이그레이션하는 전략을 제안합니다."
PM 박과장:
"npm 패키지 보안 관리는 어떻게 하죠? 의존성이 많아지면 취약점 우려가 있다고 들었는데요."
백엔드 김대리:
"npm audit으로 정기 검사하고, Snyk이나 Dependabot으로 자동 모니터링하면 됩니다. package-lock.json 관리도 철저히 해서 버전 고정하고요."
면접관:
"Node.js가 싱글 스레드인데 어떻게 수만 개의 동시 요청을 처리할 수 있는지 설명해주세요."
지원자:
"Node.js는 이벤트 루프와 논블로킹 I/O를 활용합니다. 메인 스레드는 요청을 받아 I/O 작업을 libuv의 스레드 풀에 위임하고, 바로 다음 요청을 처리합니다. I/O가 완료되면 이벤트 큐에 콜백이 등록되고, 이벤트 루프가 이를 꺼내 실행합니다."
면접관:
"이벤트 루프의 단계(Phase)에 대해서도 설명해주실 수 있나요?"
지원자:
"네, 이벤트 루프는 timers, pending callbacks, idle/prepare, poll, check, close callbacks 단계를 순환합니다. setTimeout은 timers에서, I/O 콜백은 poll에서, setImmediate는 check에서 실행됩니다. process.nextTick은 현재 단계 완료 후 바로 실행되어 가장 높은 우선순위를 가집니다."
면접관:
"CPU 바운드 작업이 많을 때 Node.js의 한계와 해결 방법은 무엇인가요?"
지원자:
"CPU 작업이 오래 걸리면 이벤트 루프가 블로킹되어 다른 요청 처리가 지연됩니다. 해결책으로는 Worker Threads로 병렬 처리하거나, 클러스터 모드로 멀티 프로세스 구성, 또는 해당 작업을 별도 서비스로 분리하는 방법이 있습니다."
리뷰어:
"이 async 함수에서 에러 핸들링이 없네요. await 하는 곳에서 에러가 발생하면 unhandled rejection이 됩니다. Node.js 15부터는 프로세스가 종료될 수도 있어요."
작성자:
"try-catch로 감싸면 될까요? 아니면 .catch()를 체이닝해야 하나요?"
리뷰어:
"async/await에서는 try-catch가 자연스럽습니다. 그리고 전역적으로 process.on('unhandledRejection')도 설정해두세요. Express 미들웨어라면 express-async-errors 패키지를 쓰면 자동으로 에러가 에러 핸들러로 전달됩니다."
중첩된 콜백은 가독성과 유지보수성을 떨어뜨립니다. async/await 또는 Promise 체이닝을 사용하여 비동기 코드를 동기 스타일로 작성하세요. util.promisify()로 콜백 기반 API를 Promise로 변환할 수 있습니다.
비동기 에러는 try-catch로 잡아야 합니다. 처리되지 않은 Promise rejection은 프로세스를 종료시킬 수 있으므로 process.on('unhandledRejection')을 설정하고, Express에서는 에러 핸들링 미들웨어를 반드시 추가하세요.
SQL Injection은 Prepared Statement나 ORM(Prisma, Sequelize)을 사용하여 방지합니다. XSS 공격은 사용자 입력을 반드시 이스케이프하고, helmet 미들웨어로 보안 헤더를 설정하세요. CSRF 토큰도 필수입니다.
npm 패키지 설치 시 npm audit으로 보안 취약점을 검사하세요. 불필요한 패키지는 제거하고, package-lock.json을 버전 관리에 포함하여 일관된 빌드를 보장하세요.