소프트웨어 기술(스타트업 위주)
node
node 정리 입니다.
1. Node.js 소개
1.1 Node.js란?
Node.js는 Chrome V8 JavaScript 엔진 위에서 동작하는 JavaScript 런타임이다. 브라우저 밖에서도 JavaScript를 실행할 수 있도록 설계되었으며, 비동기 이벤트 기반의 아키텍처를 기반으로 빠르고 확장성이 뛰어난 네트워크 애플리케이션을 개발하는 데 최적화되어 있다.
주요 특징
- 비동기 이벤트 기반: 단일 스레드 기반이지만, 비동기 I/O 처리를 통해 높은 성능을 제공
- 빠른 실행 속도: V8 엔진을 기반으로 하여 빠르게 JavaScript 코드를 실행
- 단일 스레드, 멀티플렉싱 지원: 이벤트 루프를 활용하여 비동기 처리
- 패키지 관리자 (npm) 지원: 방대한 오픈소스 라이브러리 활용 가능
1.2 Node.js를 사용하는 이유
Node.js는 서버 측 애플리케이션 개발에서 널리 사용되며, 다음과 같은 장점을 제공한다.
- 장점
- 비동기 프로그래밍: 블로킹 없이 I/O 작업을 처리하여 성능 향상
- 단일 언어 사용: 클라이언트 및 서버 모두 JavaScript로 개발 가능
- 대규모 패키지 지원: npm을 통해 방대한 모듈과 라이브러리 활용
- 높은 확장성: 경량, 빠른 응답 속도, 수평 확장이 용이
- 빠른 개발 속도: 생산성이 높아 프로토타이핑과 MVP 제작에 적합
- 단점
- CPU 집약적인 작업에 부적합: 단일 스레드 기반으로, 복잡한 연산이 많은 작업에서는 성능 저하 가능
- 비동기 코드 복잡성: 콜백 지옥(Callback Hell) 등의 문제 발생 가능
1.3 Node.js vs 브라우저
Node.js는 JavaScript 런타임이라는 점에서 브라우저에서 실행되는 JavaScript와 차이점이 있다.
구분 | Node.js | 브라우저 |
---|---|---|
실행 환경 | 서버 및 CLI | 웹 브라우저 |
API | 파일 시스템, 네트워크, 프로세스 등 | DOM, BOM |
모듈 시스템 | CommonJS, ESM | ES Modules |
비동기 처리 | 이벤트 루프 기반 | 이벤트 루프 기반 |
보안 | 제한 없음 (서버 환경) | Same-Origin Policy 적용 |
1.4 Node.js 코드 실행하기
Node.js를 설치한 후, 아래 명령어를 사용하여 JavaScript 파일을 실행할 수 있다.
- Node.js 설치 확인
node -v
- 코드 실행하기
node app.js
- 인터랙티브 모드(REPL) REPL은 Read → Eval → Print → Loop 의 줄임말로,
"한 줄씩 코드를 입력하고 바로 실행 결과를 확인할 수 있는 인터페이스" 를 말해요.
Node.js는 자체적으로 REPL을 내장하고 있어서, 터미널에서 바로 JavaScript를 실험해볼 수 있는 환경을 제공해요.
node
> console.log("Hello, Node.js!");
2. 모듈 (Modules)
2.1 ESM (ECMAScript Modules) vs CommonJS
Node.js에서는 모듈 시스템을 통해 코드의 재사용성을 높이고 관리할 수 있다. 대표적으로 CommonJS와 ESM이 있다.
CommonJS (CJS)
require()
사용module.exports
로 모듈 내보내기- 동기적으로 모듈 로딩
// math.js
module.exports = {
add: (a, b) => a + b,
}
// app.js
const math = require('./math')
console.log(math.add(2, 3))
ESM (ECMAScript Modules)
import
/export
사용- 비동기적으로 모듈 로딩 가능
- 최신 표준, ES6 이후 기본 모듈 시스템
// math.mjs
export const add = (a, b) => a + b
// app.mjs
import { add } from './math.mjs'
console.log(add(2, 3))
2.2 모듈 생성 및 가져오기
Node.js에서는 모듈을 생성하고 가져오는 방법이 다양하다.
내장 모듈 사용하기
Node.js에서 제공하는 기본 모듈을 사용할 수 있다.
const fs = require('fs')
fs.writeFileSync('test.txt', 'Hello, Node.js!')
사용자 정의 모듈 사용하기
// greet.js
module.exports = function (name) {
return `Hello, ${name}!`
}
// app.js
const greet = require('./greet')
console.log(greet('World'))
2.3 [global] 키워드
Node.js에서는 전역 객체인 global
을 통해 전역 변수나 함수를 정의할 수 있다.
global
사용 예시
global.myVar = 'Hello'
console.log(myVar) // Hello
- 주요 전역 객체
객체 | 설명 |
---|---|
global | 전역 객체 |
__dirname | 현재 파일이 위치한 디렉토리 |
__filename | 현재 파일의 전체 경로 |
process | 현재 실행 중인 프로세스 정보 |
setTimeout | 일정 시간 후 코드 실행 |
setInterval | 일정 간격으로 코드 실행 |
주의 사항
global
객체에 너무 많은 변수를 추가하면 코드 관리가 어려워질 수 있음가능하면 모듈을 사용하여 필요한 범위에서만 변수 사용 권장
3. npm
3.1 npx
정의:
3.2 글로벌 설치 (Global Installation)
정의: npm install -g <패키지>
명령어를 사용하여 패키지를 시스템 전체에 설치합니다.
장점:
- 모든 프로젝트에서 동일한 패키지를 사용할 수 있음
- CLI 도구 설치에 적합함
단점:
- 패키지 업데이트 시 모든 프로젝트에서 영향을 받을 수 있음
- 권한 문제로
sudo
가 필요할 수 있음
예시:
npm install -g nodemon
3.3 로컬 설치 (Local Installation)
정의: npm install <패키지>
를 실행하면 현재 프로젝트 디렉토리에 node_modules
폴더에 패키지가 설치됩니다.
장점:
- 프로젝트별로 의존성을 관리할 수 있음
- 글로벌 패키지 충돌을 방지할 수 있음
단점:
- 프로젝트마다 패키지를 설치해야 하므로 디스크 사용량 증가
예시:
npm install express
3.4 패키지 업데이트 (Updating Packages)
정의: npm update
명령어를 사용하여 설치된 패키지를 최신 버전으로 업데이트할 수 있음
예시:
npm update
npm update -g nodemon
특징:
npm outdated
로 업데이트 가능한 패키지 확인 가능npm update
실행 시package.json
의 버전 범위 내에서 업데이트됨
3.5 스크립트 실행 (Running Scripts)
정의: package.json
의 scripts
필드를 사용하여 커스텀 명령어를 실행할 수 있음
예시:
{
"scripts": {
"start": "node app.js",
"dev": "nodemon app.js"
}
}
실행:
npm run dev
3.6 npm 워크스페이스 (npm Workspaces)
정의: 하나의 package.json
에서 여러 패키지를 관리할 수 있는 기능
장점:
- 모노레포(여러 패키지를 하나의 리포에서 관리) 지원
- 의존성 공유 가능
예시:
{
"workspaces": ["packages/*"]
}
3.7 패키지 생성 (Creating Packages)
정의: npm init
명령어를 사용하여 새 패키지를 생성
예시:
npm init -y
3.8 시맨틱 버전 (Semantic Versioning)
정의: MAJOR.MINOR.PATCH
형식으로 버전 관리
예시:
1.0.0
→1.1.0
(마이너 업데이트)
4. 에러 처리 (Error Handling)
4.1 시스템 에러 (System Errors)
정의: 파일 접근 불가, 네트워크 오류 등의 시스템 관련 에러
예시:
const fs = require('fs')
fs.readFile('/path/to/file', (err, data) => {
if (err) console.error('파일 읽기 실패:', err)
})
4.2 사용자 지정 에러 (User Specified Errors)
정의: 개발자가 직접 정의하는 에러
예시:
class CustomError extends Error {
constructor(message) {
super(message)
this.name = 'CustomError'
}
}
throw new CustomError('이것은 사용자 정의 에러입니다.')
4.3 단언 에러 (Assertion Errors)
정의: 예상과 실제 값이 다를 때 발생하는 에러
예시:
const assert = require('assert')
assert.strictEqual(1 + 1, 2, '1 + 1은 2여야 합니다.')
4.4 자바스크립트 에러 (Javascript Errors)
정의: SyntaxError
, ReferenceError
등 자바스크립트 기본 에러
예시:
try {
nonexistentFunction()
} catch (error) {
console.error('에러 발생:', error)
}
4.5 처리되지 않은 예외 (Uncaught Exceptions)
정의: 처리되지 않은 예외가 발생할 때 이벤트 리스너 추가 가능
예시:
process.on('uncaughtException', (err) => {
console.error('처리되지 않은 예외 발생:', err)
})
4.6 비동기 에러 처리 (Handling Async Errors)
정의: try-catch
블록이 비동기 코드에서 동작하지 않으므로 catch
사용
예시:
async function fetchData() {
try {
let data = await someAsyncFunction()
} catch (error) {
console.error('비동기 에러 발생:', error)
}
}
4.7 호출 스택 (Callstack / Stack Trace)
정의: 에러가 발생한 위치를 추적하는 데 사용됨
예시:
console.trace('디버깅용 호출 스택')
4.8 디버거 사용 (Using Debugger)
정의: debugger
키워드를 사용하여 실행 중단 가능
예시:
function test() {
let x = 10
debugger
console.log(x)
}
test()
5. 비동기 프로그래밍 (Async Programming)
5.1 이벤트 발행기 (Event Emitter)
정의
이벤트 기반 프로그래밍을 구현하는 Node.js의 핵심 모듈 중 하나로, 특정 이벤트가 발생하면 등록된 리스너(핸들러)가 실행되도록 하는 기능을 제공한다.
장점
- 이벤트 기반 비동기 프로그래밍이 가능함.
- 메모리 효율적이며, 비동기 이벤트 처리를 쉽게 구현 가능.
- 다수의 이벤트 리스너 등록 및 해제가 용이함.
단점
- 이벤트가 너무 많아지면 코드가 복잡해질 수 있음.
- 디버깅이 어렵고, 이벤트 순서가 예측하기 어려울 수 있음.
예제
const EventEmitter = require('events')
class MyEmitter extends EventEmitter {}
const myEmitter = new MyEmitter()
myEmitter.on('event', () => {
console.log('이벤트 발생!')
})
myEmitter.emit('event')
5.2 이벤트 루프 (Event Loop)
정의
Node.js의 핵심 개념으로, 싱글 스레드에서 비동기 작업을 처리하는 메커니즘이다. 이벤트 루프는 콜 스택(Call Stack)과 작업 큐(Task Queue)를 관리하며, 비동기 처리를 효율적으로 실행한다. 이벤트 루프가 필요한 이유는 비동기 작업 ( fetch, setTimeOut, 클릭 이벤트)도 동시에 처리해야 하기 때문에 필요합니다.
구성 요소
Call Stack (콜 스택):
실행할 함수가 쌓이는 곳 (동기 코드가 실행됨)
Web APIs:
setTimeout
,DOM 이벤트
,fetch
같은 브라우저에서 제공하는 기능Callback Queue (태스크 큐):
Web API로 처리 완료된 작업이 들어오는 곳 (setTimeout 콜백 등)
Microtask Queue (마이크로태스크 큐):
Promise.then
,MutationObserver
같은 더 빠른 우선순위의 큐Event Loop:
콜 스택이 비어 있을 때 큐에서 콜백을 가져와 실행하는 반복 로직
- 콜 스택이 비어 있는지 확인
- Microtask Queue 먼저 처리
- 그 후 Callback Queue 처리
- 이 과정을 반복 → 루프(Loop)
console.log('1');
setTimeout(() => {
console.log('2');
}, 0);
Promise.resolve().then(() => {
console.log('3');
});
console.log('4');
실행 순서
console.log('1')
→ 콜 스택setTimeout()
→ Web API → Callback QueuePromise.then()
→ Microtask Queueconsole.log('4')
→ 콜 스택- 콜 스택이 비니까 Microtask Queue의
console.log('3')
- 그 후 Callback Queue의
console.log('2')
장점
- 높은 성능과 비동기 I/O 처리에 최적화됨.
- 논 블로킹 방식으로 빠른 응답 가능.
- 서버 성능을 극대화할 수 있음.
단점
- CPU 집약적인 작업에는 부적합.
- 비동기 코드의 흐름을 이해하기 어려움.
실무에서의 활용 예
debounce
/throttle
처리 시 렌더링 과부하 방지- 이벤트 핸들러 내에서
Promise
,setTimeout
,requestAnimationFrame
으로 성능 최적화 - 무한 스크롤, 대량 데이터 렌더링 시 task 분리 처리
5.3 프로미스 (Promises)
정의
비동기 작업의 완료 또는 실패를 나타내는 객체로, then
과 catch
를 이용해 연속적인 작업 처리가 가능하다. javascript 섹션에 자세히 설명되어 있습니다.
장점
- 콜백 지옥(Callback Hell)을 방지할 수 있음.
- 체이닝을 통해 가독성이 뛰어남.
단점
- 코드가 다소 복잡해질 수 있음.
- 사용법을 익히는 데 시간이 필요할 수 있음.
예제
const myPromise = new Promise((resolve, reject) => {
setTimeout(() => resolve('완료!'), 2000)
})
myPromise.then((result) => console.log(result))
5.4 async/await
정의
Promise를 기반으로 한 비동기 처리 방식으로, 가독성이 뛰어나고 동기적인 코드처럼 작성할 수 있음. javascript 섹션에 자세히 설명되어 있습니다.
장점
- 가독성이 뛰어나며 직관적임.
- 예외 처리가
try/catch
문을 이용하여 간편함.
단점
- 에러 처리를 신경 써야 함.
- Promise의 성능보다 약간 느릴 수 있음.
예제
async function fetchData() {
return '데이터'
}
async function main() {
const data = await fetchData()
console.log(data)
}
main()
5.5 콜백 (Callbacks)
정의
비동기 작업이 완료된 후 실행될 함수를 매개변수로 전달하는 방식.
장점
- 단순한 작업에서는 효율적.
- 이벤트 기반 비동기 처리에 최적화됨.
단점
- 콜백 중첩이 깊어질 경우 가독성이 떨어지는 "콜백 지옥" 발생.
예제
function fetchData(callback) {
setTimeout(() => callback('데이터 로드 완료'), 2000)
}
fetchData(console.log)
5.6 setTimeout, setInterval, setImmediate
정의
비동기적으로 특정 작업을 일정 시간 후(setTimeout) 또는 주기적으로(setInterval) 실행하거나, 즉시 실행(setImmediate)하도록 예약하는 기능.
예제
setTimeout(() => console.log('2초 후 실행'), 2000)
setInterval(() => console.log('1초마다 실행'), 1000)
setImmediate(() => console.log('즉시 실행'))
5.7 process.nextTick
정의
현재 실행 중인 이벤트 루프의 다음 사이클에서 콜백을 실행하도록 예약하는 함수.
예제
process.nextTick(() => {
console.log('다음 틱에서 실행')
})
console.log('현재 실행')
6. 파일 작업 (Working With Files)
6.1 **dirname, **filename
정의
현재 실행 중인 스크립트 파일의 디렉토리 경로(**dirname)와 파일 경로(**filename)를 제공.
예제
console.log(__dirname)
console.log(__filename)
6.2 glob
정의
파일 패턴 매칭을 통해 특정 확장자를 가진 파일들을 검색하는 모듈.
예제
const glob = require('glob')
glob('*.js', (err, files) => {
console.log(files)
})
6.3 globby
정의
glob보다 확장된 기능을 제공하는 파일 검색 라이브러리.
예제
const globby = require('globby')
;(async () => {
const paths = await globby(['*.js'])
console.log(paths)
})()
6.4 fs-extra
정의
기본 fs
모듈을 확장한 라이브러리로, 파일 및 디렉토리 작업을 쉽게 할 수 있도록 제공됨.
예제
const fs = require('fs-extra')
fs.copy('source.txt', 'destination.txt')
6.5 chokidar
정의
파일 변경 감지를 위한 라이브러리로, 실시간으로 파일의 추가, 변경, 삭제를 감지할 수 있음.
예제
const chokidar = require('chokidar')
const watcher = chokidar.watch('./folder')
watcher.on('add', (path) => console.log(`${path} 추가됨`))
7. Command Line Apps
7.1. Exiting / Exit codes
프로그램이 종료될 때, 종료 코드를 반환하여 정상 종료인지 오류 발생인지 나타낼 수 있다.
정의
- 종료 코드(exit code): 프로세스가 종료될 때 운영체제에 반환하는 값.
process.exit(code)
: Node.js에서 종료 코드를 설정하고 프로세스를 종료하는 함수.
장점
- 스크립트 또는 다른 프로그램이 종료 상태를 쉽게 확인할 수 있음.
- 오류 발생 시 적절한 디버깅이 가능함.
단점
process.exit(0)
을 호출하면 이후의 비동기 코드가 실행되지 않음.- 잘못된 종료 코드 사용은 디버깅을 어렵게 할 수 있음.
예시
console.log('프로그램 종료')
process.exit(0) // 정상 종료
8. 환경 변수 (Environment Variables)
8.1. process.env
정의
- Node.js에서
process.env
객체를 통해 환경 변수를 읽고 설정할 수 있다.
장점
- 보안이 필요한 정보를 코드에 직접 포함하지 않아도 됨.
- 다양한 실행 환경에서 설정을 쉽게 변경할 수 있음.
단점
- 환경 변수 관리가 필요함.
process.env
의 값은 문자열로 저장되므로 변환이 필요할 수 있음.
예시
console.log('NODE_ENV:', process.env.NODE_ENV)
8.2. dotenv 패키지
정의
.env
파일에서 환경 변수를 로드하는 패키지.
예시
require('dotenv').config()
console.log('API_KEY:', process.env.API_KEY)
8.3. 입력 받기 (Taking Input)
process.stdin
정의
- Node.js에서 표준 입력을 다룰 수 있는 스트림 객체.
예시
process.stdin.on('data', (data) => {
console.log('입력된 값:', data.toString().trim())
})
- Inquirer 패키지
정의
- 터미널에서 대화형 입력을 받을 수 있는 패키지.
예시
const inquirer = require('inquirer')
inquirer
.prompt([{ type: 'input', name: 'name', message: '이름을 입력하세요:' }])
.then((answers) => console.log('입력된 이름:', answers.name))
- prompts 패키지
정의
- 간결한 인터페이스로 입력을 받을 수 있는 패키지.
예시
const prompts = require('prompts')
;(async () => {
const response = await prompts({
type: 'text',
name: 'name',
message: '이름을 입력하세요:',
})
console.log('입력된 이름:', response.name)
})()
8.4. 출력하기 (Printing Output)
- stdout / stderr
정의
process.stdout
: 표준 출력 스트림.process.stderr
: 표준 오류 출력 스트림.
예시
process.stdout.write('일반 출력\n')
process.stderr.write('오류 출력\n')
9. API 구축
9.1 프레임워크
Express.js
정의: Express.js는 Node.js 환경에서 가장 널리 사용되는 웹 프레임워크로, 간결하고 유연한 라우팅 및 미들웨어 구성을 지원한다.
장점:
- 가볍고 빠르다.
- 미들웨어 기능이 풍부하다.
- 커뮤니티 지원이 활발하다.
단점:
- 코드 구조를 체계적으로 관리하는 데 추가적인 설계가 필요하다.
- 내장된 ORM이나 강력한 모듈 시스템이 부족하다.
예시:
const express = require('express') const app = express() app.get('/', (req, res) => { res.send('Hello World!') }) app.listen(3000, () => { console.log('Server is running on port 3000') })
특성:
- 미들웨어 중심 설계
- 경량화된 REST API 개발 가능
- 커스텀 라우터 및 핸들러 확장 가능
NestJS
정의: NestJS는 TypeScript 기반의 Node.js 프레임워크로, 모듈화된 구조와 의존성 주입을 제공하여 대규모 애플리케이션 개발에 적합하다.
장점:
- 강력한 모듈화 시스템
- TypeScript 완전 지원
- 내장된 미들웨어 및 데코레이터 기반 아키텍처
단점:
- 학습 곡선이 가파르다.
- 간단한 프로젝트에는 오버헤드가 클 수 있다.
예시:
import { Module, Controller, Get } from '@nestjs/common' import { NestFactory } from '@nestjs/core' @Controller() class AppController { @Get() getHello(): string { return 'Hello World!' } } @Module({ controllers: [AppController] }) class AppModule {} async function bootstrap() { const app = await NestFactory.create(AppModule) await app.listen(3000) } bootstrap()
특성:
- 데코레이터 기반의 API 설계
- 모듈 단위의 확장 가능성
- TypeScript 지원
9.2 인증
JSON Web Token (jsonwebtoken)
정의: JWT(JSON Web Token)는 클라이언트-서버 간 인증 및 정보 교환을 위해 사용되는 토큰 기반 인증 방식이다.
장점:
- 상태를 유지할 필요 없음 (Stateless)
- 간편한 사용자 인증 구현 가능
단점:
- 보안 키 관리 필요
- 토큰 크기가 클 경우 네트워크 부하 증가
예시:
const jwt = require('jsonwebtoken') const token = jwt.sign({ userId: 123 }, 'secret_key', { expiresIn: '1h' }) console.log(token)
특성:
- 서명된 토큰을 사용하여 인증 유지
- 클라이언트-서버 간 인증에 적합
JSON 사용 이유
사람이 읽을 수 있고, 기계도 쉽게 해석 가능 JSON은 문자열 기반이라 REST API, 브라우저, Node.js, Python 등 모든 환경에서 쉽게 처리 가능
HTTP 통신과 잘 어울림 REST API, SPA 구조에서 대부분 요청/응답 바디는 JSON 형식 사용
JWT도 JSON 기반이기 때문에 HTTP Header나 Body에 넣기 적합
- 표준화된 규격이기 때문 JWT는 RFC 7519라는 공식 표준이에요. 이 표준에서 Header, Payload는 반드시 JSON 객체여야 한다고 명시돼 있어요.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. ← Header (JSON → Base64Url) eyJ1c2VySWQiOjEyMywicm9sZSI6ImFkbWluIiwiZXhwIjoxNzEyNjcxMjkzfQ. ← Payload (JSON → Base64Url) [Signature 부분은 HMAC + 비밀키로 암호화]
- Signature만 JSON이 아닌 이유? Signature는 암호화된 해시값이기 때문에 문자열이 아니에요.
앞의 Header와 Payload는 인증 정보/권한 정보를 표현하는 JSON이고, Signature는 그 JSON이 위조되지 않았음을 증명하는 전자서명이에요.
Passport.js
정의: Passport.js는 다양한 인증 전략을 지원하는 Node.js 인증 미들웨어이다.
장점:
- 다양한 인증 방식 지원 (JWT, OAuth, Local 등)
- 확장성이 뛰어남
단점:
- 설정이 다소 복잡함
- 공식 문서가 다소 부족함
예시:
const passport = require('passport') const LocalStrategy = require('passport-local').Strategy passport.use( new LocalStrategy((username, password, done) => { if (username === 'user' && password === 'pass') { return done(null, { id: 1, username }) } return done(null, false) }), )
특성:
- 다양한 인증 전략 플러그인 지원
- 미들웨어 방식으로 API에 적용 가능
9.3 API 호출 방법
HTTP 모듈
정의: Node.js의 기본 HTTP 모듈을 사용하여 API 요청을 보낼 수 있다.
예시:
const http = require('http') http.get('http://example.com', (res) => { res.on('data', (chunk) => console.log(chunk.toString())) })
Axios
정의: Promise 기반의 HTTP 요청 라이브러리로, 간편한 API 호출 기능을 제공한다.
예시:
const axios = require('axios') axios.get('https://api.example.com/data').then((res) => console.log(res.data))
Fetch
정의: JavaScript의 기본 제공 fetch API를 활용하여 네트워크 요청을 처리할 수 있다.
예시:
fetch('https://api.example.com/data') .then((response) => response.json()) .then((data) => console.log(data))
Got
정의: Node.js 환경에서 사용할 수 있는 강력한 HTTP 요청 라이브러리이다.
예시:
const got = require('got') ;(async () => { const response = await got('https://api.example.com/data').json() console.log(response) })()
9.4 개발 시 변경 감시
--watch
정의: Node.js에서 파일 변경을 감지하여 자동으로 코드가 반영되도록 하는 옵션이다.
예시:
node --watch index.js
Nodemon
정의: Node.js 애플리케이션의 변경 사항을 자동으로 감지하고 서버를 재시작하는 도구이다.
예시:
nodemon app.js
10. 데이터베이스
10.1 Mongoose
정의
Mongoose는 MongoDB를 위한 객체 데이터 모델링(ODM) 라이브러리로, 스키마 기반으로 MongoDB 데이터를 구조화하고 검증할 수 있도록 도와준다.
장점
- 스키마를 통해 데이터 구조를 명확히 정의 가능
- 데이터 검증 및 미들웨어 기능 지원
- 관계형 데이터 모델링을 흉내낼 수 있음 (Population)
- 편리한 쿼리 빌더 제공
단점
- MongoDB의 유연한 데이터 모델을 제한할 수 있음
- 학습 곡선이 있을 수 있음
예시
const mongoose = require('mongoose')
const userSchema = new mongoose.Schema({
name: String,
email: String,
age: Number,
})
const User = mongoose.model('User', userSchema)
mongoose.connect('mongodb://localhost:27017/mydb', {
useNewUrlParser: true,
useUnifiedTopology: true,
})
10.2 Prisma
정의
Prisma는 TypeScript와 함께 사용되는 ORM으로, SQL 및 NoSQL 데이터베이스를 쉽게 관리할 수 있도록 도와준다.
장점
- 타입 안전성이 뛰어나며 TypeScript와 자연스럽게 통합됨
- 강력한 마이그레이션 기능 제공
- GraphQL 및 REST API와 쉽게 통합 가능
단점
- 성능이 ORM 중간 수준
- 고급 SQL 기능 활용이 어렵거나 제한될 수 있음
예시
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
const user = await prisma.user.create({
data: { name: 'Alice', email: 'alice@example.com' },
})
console.log(user)
}
main()
.catch((e) => console.error(e))
.finally(async () => {
await prisma.$disconnect()
})
10.3 TypeORM
정의
TypeORM은 Node.js 및 TypeScript에서 사용할 수 있는 ORM으로, Active Record 및 Data Mapper 패턴을 지원한다.
장점
- 다양한 관계형 데이터베이스 지원
- 엔터티 기반의 직관적인 데이터 모델링 가능
- 마이그레이션 및 데이터 검증 기능 제공
단점
- 복잡한 설정 필요
- 성능이 순수 SQL보다 떨어질 수 있음
예시
import { Entity, PrimaryGeneratedColumn, Column } from 'typeorm'
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number
@Column()
name: string
@Column()
email: string
}
10.4 Sequelize
정의
Sequelize는 Promise 기반의 ORM으로, MySQL, PostgreSQL, SQLite 등을 지원한다.
장점
- 다양한 데이터베이스 지원
- 쿼리 빌더 제공
- 트랜잭션 관리 기능 지원
단점
- TypeORM, Prisma에 비해 성능이 낮음
- 데이터 모델링이 비교적 복잡할 수 있음
예시
const { Sequelize, DataTypes } = require('sequelize')
const sequelize = new Sequelize('sqlite::memory:')
const User = sequelize.define('User', {
name: {
type: DataTypes.STRING,
allowNull: false,
},
email: {
type: DataTypes.STRING,
allowNull: false,
},
})
;(async () => {
await sequelize.sync()
const user = await User.create({ name: 'Alice', email: 'alice@example.com' })
console.log(user.toJSON())
})()
11. 로깅
11.1 Winston
정의
Winston은 다목적 로깅 라이브러리로, 여러 로그 전송 방식을 지원한다.
장점
- 다양한 로그 전송 방식 지원 (파일, 콘솔, DB 등)
- 포맷팅 및 수준 조절 가능
단점
- 기본 설정이 다소 복잡할 수 있음
예시
const winston = require('winston')
const logger = winston.createLogger({
level: 'info',
transports: [
new winston.transports.Console(),
new winston.transports.File({ filename: 'app.log' }),
],
})
logger.info('정보 로그')
11.2 Morgan
정의
Morgan은 HTTP 요청 로깅을 위한 미들웨어로, Express 애플리케이션에서 주로 사용된다.
장점
- 간단한 설정으로 요청 로그 기록 가능
- 다양한 프리셋 지원 (combined, common, tiny 등)
단점
- 로그 보관 및 분석 기능이 없음
예시
const express = require('express')
const morgan = require('morgan')
const app = express()
app.use(morgan('combined'))
app.get('/', (req, res) => res.send('Hello World!'))
app.listen(3000)
12. 애플리케이션 지속 실행
12.1 PM2
정의
PM2는 Node.js 애플리케이션을 지속적으로 실행하고, 자동 재시작 기능을 제공하는 프로세스 매니저다.
장점
- 애플리케이션 충돌 시 자동 재시작
- 로드 밸런싱 및 모니터링 기능 제공
단점
- 시스템 자원을 추가로 소모할 수 있음
예시
pm2 start app.js --name myApp
13. Threads
13.1 Child Process
정의
Node.js에서 child_process
모듈을 사용하면 별도의 프로세스를 생성하여 멀티스레딩 환경을 시뮬레이션할 수 있다.
장단점
- 장점
- 메인 이벤트 루프의 부하를 줄일 수 있음
- 별도의 프로세스로 실행되므로 병렬 처리가 가능
- 단점
- 프로세스 간 통신이 필요하여 성능 오버헤드가 발생할 수 있음
예제
아래에 Node.js의 child_process 모듈을 이용해서 외부 프로그램을 실행하는 실전 예제 3개를 공부해 봤습니다.
const { spawn } = require('child_process');
// 'ls -lh /usr' 명령 실행
const ls = spawn('ls', ['-lh', '/usr']);
ls.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ls.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ls.on('close', (code) => {
console.log(`프로세스 종료 코드: ${code}`);
});
// run-python.js
const { spawn } = require('child_process');
// 실행할 Python 스크립트
const python = spawn('python3', ['hello.py', 'ChatGPT']);
python.stdout.on('data', (data) => {
console.log(`Python 응답: ${data.toString()}`);
});
python.stderr.on('data', (data) => {
console.error(`에러: ${data}`);
});
python.on('close', (code) => {
console.log(`종료 코드: ${code}`);
});
// ffmpeg로 동영상에서 mp3 추출
const { spawn } = require('child_process');
const ffmpeg = spawn('ffmpeg', [
'-i', 'video.mp4',
'-vn',
'-acodec', 'libmp3lame',
'-ab', '192k',
'-ar', '44100',
'output.mp3',
]);
ffmpeg.stdout.on('data', (data) => {
console.log(`stdout: ${data}`);
});
ffmpeg.stderr.on('data', (data) => {
console.error(`stderr: ${data}`);
});
ffmpeg.on('close', (code) => {
console.log(`변환 완료. 종료 코드: ${code}`);
});
요약
- 리눅스 명령 실행
- 파이썬 호출
- 빌드 자동화
- 미디어 변환
13.2 Cluster
정의
Node.js의 cluster
모듈을 활용하여 단일 프로세스에서 여러 개의 워커 프로세스를 생성하여 멀티코어 활용을 최적화할 수 있다.
장단점
- 장점
- 멀티코어를 활용하여 성능 향상
- 각 프로세스가 독립적으로 실행되어 하나의 워커가 죽어도 다른 워커에는 영향 없음
- 단점
- 프로세스 간 메모리 공유가 어려움
- IPC(Inter-Process Communication)가 필요함
예제
const cluster = require('cluster')
const http = require('http')
const numCPUs = require('os').cpus().length
if (cluster.isMaster) {
for (let i = 0; i < numCPUs; i++) {
cluster.fork()
}
} else {
http
.createServer((req, res) => {
res.writeHead(200)
res.end('Hello World\n')
})
.listen(8000)
}
13.3 Worker Threads
구현하기 복잡해서 저는 잘 안씁니다, 대안방법으로는 AWS lambda 함수와 같은 서버리스 함수를 이용합니다.
정의
worker_threads
모듈을 사용하여 Node.js 내에서 멀티스레딩을 구현할 수 있다.
장단점
- 장점
- 동일한 메모리 공간을 공유할 수 있어 프로세스 간 통신보다 빠름
- CPU 집약적인 작업을 병렬 처리할 수 있음
- 단점
- 싱글 스레드 환경에서 발생하지 않는 동기화 문제 발생 가능
예제
const { Worker, isMainThread, parentPort } = require('worker_threads')
if (isMainThread) {
const worker = new Worker(__filename)
worker.on('message', (msg) => console.log('Worker message:', msg))
} else {
parentPort.postMessage('Hello from worker')
}
14. Streams
정의
Node.js의 stream
모듈은 데이터 처리를 효율적으로 수행하기 위한 API를 제공하며, 데이터를 작은 조각으로 나누어 처리할 수 있다. stream 은 파일 처리, 네트워크 통신, 영상/음성 스트리밍 등에 자주 사용됩니다. 연관 개념으로 buffer의 개념을 알고 있으면 좋아요. buffer 는 이진 데이터를 메모리에 담는 객체(파일 이미지) 를 말하는 것입니다.
작은 파일 읽기는 Buffer : fs.readFileSync 대용량 파일/네트워크 처리 Stream : fs.createReadStream 으로 처리를 권장합니다.
장단점
- 장점
- 메모리 사용량 절감
- I/O 성능 최적화
- 단점
- 이벤트 기반이므로 코드가 복잡해질 수 있음
예제
const fs = require('fs')
const readStream = fs.createReadStream('input.txt')
readStream.on('data', (chunk) => {
console.log('Chunk:', chunk)
})
15. Debugging
15.1 Memory Leaks
정의
메모리 누수는 더 이상 필요하지 않은 객체가 해제되지 않고 지속적으로 메모리를 점유하는 현상을 의미한다. 더 이상 사용하지 않는 데이터나 객체가 메모리에서 해제되지 않고 계속 남아 있는 현상입니다.
즉, **"쓰지 않는데 계속 메모리를 차지하고 있는 상태"**예요. 결국 → 메모리 사용량이 계속 증가 → 서버가 느려지거나 죽을 수 있어요.
- 다음과 같은 상황에서 자주 발생해요.
- 전역 변수 남발 global.cache = {} 등 계속 쌓임
- 클로저 내부에 참조가 남아 있음 함수 스코프 안에 안 쓰는 객체가 살아있음
- 이벤트 리스너 해제 안 함 emitter.on()은 했는데 off() 안 함
- 캐시 누적 LRU 캐시, Redis, 메모리 캐시가 정리 안 됨
- 타이머/인터벌 미정리 setInterval()은 있는데 clearInterval() 안 함
- DOM 노드 참조 유지 프론트에서는 사용 안 해도 참조가 남아 있을 때
모니터링 방법
heapdump
모듈을 활용한 힙 스냅샷 분석process.memoryUsage()
를 통한 메모리 사용량 모니터링실제 해결 대안
- 사용 안 하는 변수 null 처리 참조 끊기 (GC 가능하도록)
- 이벤트 리스너 정리 removeListener, off 등 호출
- 캐시 크기 제한 메모리 캐시 사용 시 갯수/시간 제한
- setInterval, setTimeout 정리 clearInterval, clearTimeout 명확히 호출
- process.memoryUsage() 주기적 모니터링 사용량 증가 패턴 추적
- heapdump, clinic.js, chrome DevTools 실제 누수 지점 디버깅 도구 활용
15.2 node --inspect
전 자주사용하진 않아요. 그냥 알고 있으면 좋다 이정도 입니다.
정의
Node.js에서 기본 제공하는 디버깅 도구로, Chrome DevTools와 연동하여 디버깅이 가능하다.
사용법
node --inspect script.js
15.3 Using APM
정의
Application Performance Monitoring(APM) 도구를 활용하여 Node.js 애플리케이션의 성능을 모니터링할 수 있다.
- New Relic 기업용, 시각화 우수, 다양한 언어 지원
- Datadog APM 클라우드 네이티브에 강함, AWS 연동 탁월
- Elastic APM 오픈소스, Kibana와 연동해서 시각화
- Sentry 주로 에러 추적에 강함 (APM 기능도 있음)
- SkyWalking, Jaeger 오픈소스 기반, 분산 추적 트래킹용
- PM2 + Keymetrics Node.js에 특화된 간단한 모니터링 대시보드
-> Elastic APM (완전 무료, 오픈소스) 추천 드립니다.
- APM 도구를 사용하는 이점
- 느려진 요청의 원인을 알 수 있음
- 트랜잭션 단위 추적
- 메서드, 쿼리 단위 성능 분석
- 시각화 대시보드 제공
AWS cloudwatch 와 차이점
구분 | APM 도구 | CloudWatch |
---|---|---|
목적 | 애플리케이션 성능/에러 중심 | 시스템 자원/로그 중심 |
추적 범위 | API, 쿼리, 트랜잭션, 코드라인 | 인스턴스 상태, 로그 발생량 |
대표 도구 | Elastic APM, Sentry, Datadog | AWS CloudWatch, X-Ray |
15.4 Garbage Collection
정의
Node.js는 V8 엔진의 GC를 사용하여 불필요한 메모리를 자동으로 해제한다.
강제 실행
node --expose-gc
global.gc()
16. Common Built-in Modules
16.1 fs 모듈
정의
파일 시스템 작업을 처리하는 모듈
예제
const fs = require('fs')
fs.readFile('file.txt', 'utf8', (err, data) => {
console.log(data)
})
인코딩 관련 개념입니다.
- UTF-8, ASCII 문자 인코딩 (한글, 영어 등)
- Base64 바이너리 → 문자열
- URL Encoding 웹 주소에 특수문자 안전하게 넣기
- gzip, deflate 텍스트 압축
- mp3, jpg, mp4 미디어 압축 인코딩 (손실/비손실 압축)
16.2 url 모듈
정의
URL을 다루기 위한 유틸리티 모듈
예제
const url = require('url')
const parsedUrl = url.parse('https://example.com/path?name=John')
console.log(parsedUrl.query)
16.3 console 모듈
정의
로그 출력 관련 기능 제공
16.16 util 모듈
정의
다양한 유틸리티 함수 제공 (예: promisify
)
16.5 event 모듈
정의
이벤트 기반 프로그래밍을 위한 모듈
예제
const EventEmitter = require('events')
const emitter = new EventEmitter()
emitter.on('event', () => console.log('Event triggered'))
emitter.emit('event')
16.6 os 모듈
정의
운영 체제 관련 정보 제공
예제
const os = require('os')
console.log(os.platform())
16.7 worker_threads 모듈
정의
멀티스레드 작업을 위한 모듈
16.8 child_process 모듈
정의
자식 프로세스를 생성하여 병렬 처리 가능
16.9 process 객체
정의
현재 실행 중인 Node.js 프로세스 정보 제공
16.10 crypto 모듈
정의
암호화 및 해싱 기능 제공
예제
const crypto = require('crypto')
const hash = crypto.createHash('sha256').update('password').digest('hex')
console.log(hash)