소프트웨어 기술(스타트업 위주)

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는 서버 측 애플리케이션 개발에서 널리 사용되며, 다음과 같은 장점을 제공한다.

  • 장점
  1. 비동기 프로그래밍: 블로킹 없이 I/O 작업을 처리하여 성능 향상
  2. 단일 언어 사용: 클라이언트 및 서버 모두 JavaScript로 개발 가능
  3. 대규모 패키지 지원: npm을 통해 방대한 모듈과 라이브러리 활용
  4. 높은 확장성: 경량, 빠른 응답 속도, 수평 확장이 용이
  5. 빠른 개발 속도: 생산성이 높아 프로토타이핑과 MVP 제작에 적합
  • 단점
  1. CPU 집약적인 작업에 부적합: 단일 스레드 기반으로, 복잡한 연산이 많은 작업에서는 성능 저하 가능
  2. 비동기 코드 복잡성: 콜백 지옥(Callback Hell) 등의 문제 발생 가능

1.3 Node.js vs 브라우저

Node.js는 JavaScript 런타임이라는 점에서 브라우저에서 실행되는 JavaScript와 차이점이 있다.

구분Node.js브라우저
실행 환경서버 및 CLI웹 브라우저
API파일 시스템, 네트워크, 프로세스 등DOM, BOM
모듈 시스템CommonJS, ESMES 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.jsonscripts 필드를 사용하여 커스텀 명령어를 실행할 수 있음

예시:

{
  "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.01.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:

    콜 스택이 비어 있을 때 큐에서 콜백을 가져와 실행하는 반복 로직

  1. 콜 스택이 비어 있는지 확인
  2. Microtask Queue 먼저 처리
  3. 그 후 Callback Queue 처리
  4. 이 과정을 반복 → 루프(Loop)
console.log('1');

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

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

console.log('4');

실행 순서

  1. console.log('1')콜 스택
  2. setTimeout()Web API → Callback Queue
  3. Promise.then()Microtask Queue
  4. console.log('4')콜 스택
  5. 콜 스택이 비니까 Microtask Queue의 console.log('3')
  6. 그 후 Callback Queue의 console.log('2')

장점

  • 높은 성능과 비동기 I/O 처리에 최적화됨.
  • 논 블로킹 방식으로 빠른 응답 가능.
  • 서버 성능을 극대화할 수 있음.

단점

  • CPU 집약적인 작업에는 부적합.
  • 비동기 코드의 흐름을 이해하기 어려움.

실무에서의 활용 예

  • debounce/throttle 처리 시 렌더링 과부하 방지
  • 이벤트 핸들러 내에서 Promise, setTimeout, requestAnimationFrame으로 성능 최적화
  • 무한 스크롤, 대량 데이터 렌더링 시 task 분리 처리

5.3 프로미스 (Promises)

정의

비동기 작업의 완료 또는 실패를 나타내는 객체로, thencatch를 이용해 연속적인 작업 처리가 가능하다. 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 사용 이유

  1. 사람이 읽을 수 있고, 기계도 쉽게 해석 가능 JSON은 문자열 기반이라 REST API, 브라우저, Node.js, Python 등 모든 환경에서 쉽게 처리 가능

  2. HTTP 통신과 잘 어울림 REST API, SPA 구조에서 대부분 요청/응답 바디는 JSON 형식 사용

JWT도 JSON 기반이기 때문에 HTTP Header나 Body에 넣기 적합

  1. 표준화된 규격이기 때문 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}`);
});

요약

  1. 리눅스 명령 실행
  2. 파이썬 호출
  3. 빌드 자동화
  4. 미디어 변환

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

정의

메모리 누수는 더 이상 필요하지 않은 객체가 해제되지 않고 지속적으로 메모리를 점유하는 현상을 의미한다. 더 이상 사용하지 않는 데이터나 객체가 메모리에서 해제되지 않고 계속 남아 있는 현상입니다.

즉, **"쓰지 않는데 계속 메모리를 차지하고 있는 상태"**예요. 결국 → 메모리 사용량이 계속 증가 → 서버가 느려지거나 죽을 수 있어요.

  • 다음과 같은 상황에서 자주 발생해요.
  1. 전역 변수 남발 global.cache = {} 등 계속 쌓임
  2. 클로저 내부에 참조가 남아 있음 함수 스코프 안에 안 쓰는 객체가 살아있음
  3. 이벤트 리스너 해제 안 함 emitter.on()은 했는데 off() 안 함
  4. 캐시 누적 LRU 캐시, Redis, 메모리 캐시가 정리 안 됨
  5. 타이머/인터벌 미정리 setInterval()은 있는데 clearInterval() 안 함
  6. DOM 노드 참조 유지 프론트에서는 사용 안 해도 참조가 남아 있을 때

모니터링 방법

  • heapdump 모듈을 활용한 힙 스냅샷 분석

  • process.memoryUsage()를 통한 메모리 사용량 모니터링

  • 실제 해결 대안

  1. 사용 안 하는 변수 null 처리 참조 끊기 (GC 가능하도록)
  2. 이벤트 리스너 정리 removeListener, off 등 호출
  3. 캐시 크기 제한 메모리 캐시 사용 시 갯수/시간 제한
  4. setInterval, setTimeout 정리 clearInterval, clearTimeout 명확히 호출
  5. process.memoryUsage() 주기적 모니터링 사용량 증가 패턴 추적
  6. 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 애플리케이션의 성능을 모니터링할 수 있다.

  1. New Relic 기업용, 시각화 우수, 다양한 언어 지원
  2. Datadog APM 클라우드 네이티브에 강함, AWS 연동 탁월
  3. Elastic APM 오픈소스, Kibana와 연동해서 시각화
  4. Sentry 주로 에러 추적에 강함 (APM 기능도 있음)
  5. SkyWalking, Jaeger 오픈소스 기반, 분산 추적 트래킹용
  6. PM2 + Keymetrics Node.js에 특화된 간단한 모니터링 대시보드

-> Elastic APM (완전 무료, 오픈소스) 추천 드립니다.

  • APM 도구를 사용하는 이점
  1. 느려진 요청의 원인을 알 수 있음
  2. 트랜잭션 단위 추적
  3. 메서드, 쿼리 단위 성능 분석
  4. 시각화 대시보드 제공

AWS cloudwatch 와 차이점

구분APM 도구CloudWatch
목적애플리케이션 성능/에러 중심시스템 자원/로그 중심
추적 범위API, 쿼리, 트랜잭션, 코드라인인스턴스 상태, 로그 발생량
대표 도구Elastic APM, Sentry, DatadogAWS 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)
})

인코딩 관련 개념입니다.

  1. UTF-8, ASCII 문자 인코딩 (한글, 영어 등)
  2. Base64 바이너리 → 문자열
  3. URL Encoding 웹 주소에 특수문자 안전하게 넣기
  4. gzip, deflate 텍스트 압축
  5. 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)
Previous
vue
Next
nest