프로그래밍/Node.js

Node.js 핵심 정복: 주요 모듈 완벽 가이드

shimdh 2025. 2. 18. 09:21
728x90

1. HTTP 모듈: 웹 서버 구축의 시작

HTTP 모듈은 Node.js에서 웹 서버와 클라이언트 간의 통신을 담당하는 핵심 모듈입니다. HTTP 프로토콜을 기반으로 요청과 응답을 처리하는 기능을 제공하여, 웹 서버를 쉽게 구축하고 클라이언트와 통신할 수 있도록 지원합니다.

1.1 HTTP 모듈 개요

HTTP 모듈은 Node.js에 내장된 모듈 중 하나로, HTTP 프로토콜을 사용하여 웹 서버를 구축하고 클라이언트와 데이터를 주고받을 수 있는 기능을 제공합니다. 이를 통해 개발자는 간편하게 웹 서버를 구축하고, 클라이언트의 요청을 처리하고 응답을 전송할 수 있습니다.

1.2 핵심 개념: 요청과 응답

  • 요청 (Request): 클라이언트가 서버에 보내는 데이터입니다. 예를 들어, 사용자가 웹 브라우저에 URL을 입력하면 해당 주소로 GET 요청이 전송됩니다.
  • 응답 (Response): 서버가 클라이언트에게 보내는 데이터입니다. HTML 페이지, JSON 데이터 등 다양한 형태로 제공될 수 있습니다.

1.3 간단한 웹 서버 구축

HTTP 모듈을 사용하여 간단한 웹 서버를 구축하는 예제를 살펴봅니다.

const http = require('http'); // HTTP 모듈 불러오기

// 서버 생성
const server = http.createServer((req, res) => {
    res.statusCode = 200; // 상태 코드 설정 (200: 성공)
    res.setHeader('Content-Type', 'text/plain'); // 응답 헤더 설정
    res.end('안녕하세요! Node.js 웹 서버입니다.\n'); // 응답 내용 전송
});

// 포트 3000에서 요청 대기
server.listen(3000, () => {
    console.log('서버가 http://localhost:3000/ 에서 실행 중입니다.');
});

위 코드를 실행하면 http://localhost:3000/에 접속했을 때 "안녕하세요! Node.js 웹 서버입니다."라는 메시지를 확인할 수 있습니다.

1.4 요청 및 응답 객체 상세 분석

  • 요청 객체 (req):
    • req.method: 요청 방식 (GET, POST 등)을 나타냅니다.
    • req.url: 요청된 URL 경로를 포함합니다.
    • req.headers: 요청 헤더 정보를 포함합니다.
  • 응답 객체 (res):
    • res.statusCode: 응답 상태 코드를 설정합니다.
    • res.setHeader(name, value): 응답 헤더를 설정합니다.
    • res.writeHead(statusCode[, statusMessage][, headers]): 상태 코드와 헤더 정보를 한 번에 설정합니다.
    • res.write(chunk[, encoding][, callback]): 응답 데이터를 전송합니다. (여러 번 호출 가능)
    • res.end([data][, encoding]): 응답 데이터를 전송하고 연결을 종료합니다.

1.5 다양한 요청 처리 예제

예제 1: URL 경로에 따른 응답 처리

const http = require('http');

const server = http.createServer((req, res) => {
    if (req.method === 'GET' && req.url === '/hello') {
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.end('안녕하세요!');
    } else if (req.method === 'GET' && req.url === '/about') {
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.end('Node.js로 만든 웹 서버입니다.');
    } else {
        res.writeHead(404, { 'Content-Type': 'text/plain' });
        res.end('페이지를 찾을 수 없습니다.');
    }
});

server.listen(3000, () => {
    console.log('서버가 http://localhost:3000/ 에서 실행 중입니다.');
});

예제 2: JSON 데이터 응답

const http = require('http');

const server = http.createServer((req, res) => {
    if (req.method === 'GET' && req.url === '/api/user') {
        const user = {
            name: 'John Doe',
            age: 30,
            city: 'New York'
        };
        res.writeHead(200, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(user)); // JSON 객체를 문자열로 변환하여 응답
    } else {
        res.writeHead(404, { 'Content-Type': 'text/plain' });
        res.end('Not Found');
    }
});

server.listen(3000, () => {
    console.log('서버가 http://localhost:3000/ 에서 실행 중입니다.');
});

예제 3: HTML 응답

const http = require('http');

const server = http.createServer((req, res) => {
    if (req.method === 'GET' && req.url === '/') {
        res.writeHead(200, { 'Content-Type': 'text/html' });
        res.end('<h1>Hello, World!</h1><p>This is a simple Node.js web server.</p>');
    } else {
        res.writeHead(404, { 'Content-Type': 'text/plain' });
        res.end('Not Found');
    }
});

server.listen(3000, () => {
    console.log('서버가 http://localhost:3000/ 에서 실행 중입니다.');
});

예제 4: 요청 헤더 확인

const http = require('http');

const server = http.createServer((req, res) => {
    const userAgent = req.headers['user-agent']; // User-Agent 헤더 읽기
    console.log('User-Agent:', userAgent);

    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Request headers logged.');
});

server.listen(3000, () => {
    console.log('서버가 http://localhost:3000/ 에서 실행 중입니다.');
});

2. 파일 시스템 모듈: 데이터 관리의 핵심

파일 시스템 모듈(fs)은 Node.js 애플리케이션이 로컬 파일 시스템과 상호작용하도록 돕는 핵심 모듈입니다. 파일 및 디렉토리 생성, 읽기, 쓰기, 삭제 등 파일 시스템 관련 작업을 수행하는 데 필수적인 기능을 제공합니다.

2.1 파일 시스템 모듈 소개

파일 시스템 모듈은 Node.js 애플리케이션이 로컬 파일 시스템과 상호작용할 수 있도록 다양한 기능을 제공합니다. 이를 통해 개발자는 다음과 같은 작업을 쉽게 수행할 수 있습니다.

  • 파일 읽기 및 쓰기
  • 디렉토리 생성 및 삭제
  • 파일 메타데이터 조회

2.2 파일 읽기 및 쓰기

1) 비동기 파일 읽기 (fs.readFile)

const fs = require('fs');

fs.readFile('example.txt', 'utf8', (err, data) => {
    if (err) {
        console.error('파일 읽기 오류:', err);
        return;
    }
    console.log('파일 내용:', data); // example.txt의 내용 출력
});

2) 파일 쓰기 (fs.writeFile)

const fs = require('fs');

const content = 'Node.js 파일 시스템 모듈 사용 예제';

fs.writeFile('output.txt', content, (err) => {
    if (err) {
        console.error('파일 쓰기 오류:', err);
        return;
    }
    console.log('파일 쓰기 완료!');
});

3) 파일에 데이터 추가 (fs.appendFile)

const fs = require('fs');

const dataToAppend = '\n추가할 내용입니다.';

fs.appendFile('example.txt', dataToAppend, 'utf8', (err) => {
    if (err) {
        console.error('데이터 추가 오류:', err);
        return;
    }
    console.log('데이터 추가 완료!');
});

2.3 디렉토리 조작

1) 디렉토리 생성 (fs.mkdir)

const fs = require('fs');

fs.mkdir('myDirectory', { recursive: true }, (err) => {
    if (err) {
        console.error('디렉토리 생성 오류:', err);
        return;
    }
    console.log('디렉토리 생성 완료!');
});

2) 디렉토리 내용 읽기 (fs.readdir)

const fs = require('fs');

const directoryPath = './'; // 현재 디렉토리

fs.readdir(directoryPath, (err, files) => {
    if (err) {
        console.error('디렉토리 읽기 오류:', err);
        return;
    }
    console.log('디렉토리 내용:', files);
});

2.4 파일 및 디렉토리 정보 확인

1) 파일 존재 여부 확인 (fs.existsSync)

const fs = require('fs');

const filePath = 'example.txt';

if (fs.existsSync(filePath)) {
    console.log('파일이 존재합니다.');
} else {
    console.log('파일이 존재하지 않습니다.');
}

2.5 동기 vs. 비동기: 성능 최적화

Node.js 파일 시스템 모듈은 대부분의 함수에 대해 동기 및 비동기 버전을 제공합니다.

  • 비동기 함수: 논블로킹 방식으로 작동하여, 파일 입출력 작업 중에도 다른 코드를 실행할 수 있습니다.
  • 동기 함수: 블로킹 방식으로 작동하여, 파일 입출력 작업이 완료될 때까지 다음 코드로 진행하지 않습니다.

예제: 동기 파일 읽기 (fs.readFileSync)

const fs = require('fs');

try {
    const data = fs.readFileSync('example.txt', 'utf8');
    console.log('파일 내용 (동기):', data);
} catch (err) {
    console.error('파일 읽기 오류 (동기):', err);
}

주의 사항:

  • 동기 함수는 사용하기 간편하지만, 대용량 파일 처리 시 성능 저하를 유발할 수 있습니다.
  • Node.js의 비동기 이벤트 기반 모델을 최대한 활용하기 위해, 가능한 비동기 함수를 사용하는 것이 좋습니다.

3. 이벤트 모듈: 비동기 프로그래밍의 핵심

이벤트 기반 프로그래밍은 Node.js의 핵심 개념 중 하나입니다. 이벤트 모듈은 이러한 이벤트 기반 시스템을 구현하기 위한 도구를 제공하여, 비동기 작업을 효율적으로 처리하고 관리할 수 있도록 지원합니다.

3.1 이벤트: 변화를 감지하는 신호

이벤트는 특정한 동작이나 상태 변화를 나타내는 신호입니다. 예를 들어, 버튼 클릭, 파일 읽기 완료, 네트워크 요청 수신 등은 모두 이벤트로 간주될 수 있습니다. 이벤트 기반 프로그래밍에서는 이러한 이벤트를 감지하고, 이벤트 발생 시 특정 동작을 수행하도록 코드를 작성합니다.

3.2 EventEmitter 클래스: 이벤트 생성 및 관리

Node.js의 events 모듈은 EventEmitter 클래스를 제공하여 사용자 정의 객체에서 쉽게 이벤트를 생성하고 관리할 수 있도록 지원합니다.

주요 메서드:

  • on(eventName, listener): 지정된 이름의 이벤트에 대한 리스너 (처리 함수)를 등록합니다.
  • emit(eventName[, ...args]): 해당 이름의 이벤트를 발생시키고 등록된 리스너에게 인수를 전달합니다.
  • removeListener(eventName, listener): 특정 리스너를 제거합니다.
  • once(eventName, listener): 지정된 이름의 이벤트에 대한 일회성 리스너를 등록합니다. 이벤트가 처음 발생하면 리스너가 실행되고 자동으로 제거됩니다.
  • removeAllListeners([eventName]): 특정 이벤트 또는 모든 이벤트에 대한 모든 리스너를 제거합니다.

3.3 EventEmitter 활용 예제

예제 1: 카운터

const EventEmitter = require('events');

class Counter extends EventEmitter {
    constructor() {
        super();
        this.count = 0;
    }

    increment() {
        this.count++;
        this.emit('increment', this.count); // 'increment' 이벤트 발생
    }
}

const myCounter = new Counter();

// 'increment' 이벤트 리스너 등록
myCounter.on('increment', (count) => {
    console.log(`카운트 증가: ${count}`);
});

myCounter.increment(); // 카운트 증가: 1
myCounter.increment(); // 카운트 증가: 2
myCounter.increment(); // 카운트 증가: 3

예제 2: 파일 읽기 완료 이벤트 처리

const fs = require('fs');
const EventEmitter = require('events');

class FileReader extends EventEmitter {
    constructor(filePath) {
        super();
        this.filePath = filePath;
    }

    readFile() {
        fs.readFile(this.filePath, 'utf8', (err, data) => {
            if (err) {
                this.emit('error', err); // 에러 이벤트 발생
            } else {
                this.emit('data', data); // 데이터 이벤트 발생
            }
        });
    }
}

const reader = new FileReader('example.txt');

reader.on('data', (data) => {
    console.log('파일 읽기 완료:', data);
});

reader.on('error', (err) => {
    console.error('파일 읽기 오류:', err);
});

reader.readFile();

예제 3: 사용자 정의 이벤트 - 로그인 이벤트

const EventEmitter = require('events');

class UserAuth extends EventEmitter {
    login(username, password) {
        // 로그인 로직 (여기서는 생략)
        console.log(`${username}님이 로그인했습니다.`);
        this.emit('login', username); // 'login' 이벤트 발생
    }
}

const auth = new UserAuth();

auth.on('login', (username) => {
    console.log(`로그인 이벤트 발생: ${username}님 환영합니다!`);
});

auth.login('johndoe', 'password123');

예제 4: once를 사용한 일회성 이벤트 처리

const EventEmitter = require('events');

const emitter = new EventEmitter();

emitter.once('eventOnce', () => {
    console.log('이 이벤트는 한 번만 실행됩니다.');
});

emitter.emit('eventOnce'); // 이 이벤트는 한 번만 실행됩니다.
emitter.emit('eventOnce'); // 이 이벤트는 더 이상 실행되지 않습니다.

예제 5: removeAllListeners를 사용한 리스너 제거

const EventEmitter = require('events');

const emitter = new EventEmitter();

const listener1 = () => console.log('리스너 1');
const listener2 = () => console.log('리스너 2');

emitter.on('myEvent', listener1);
emitter.on('myEvent', listener2);

emitter.emit('myEvent'); // 리스너 1, 리스너 2 실행

emitter.removeAllListeners('myEvent'); // 'myEvent'에 대한 모든 리스너 제거

emitter.emit('myEvent'); // 아무 리스너도 실행되지 않음

3.4 이벤트 모듈 활용 분야

  • 실시간 애플리케이션: 채팅 애플리케이션에서 메시지 수신 이벤트를 처리하여 사용자에게 실시간 알림을 제공할 수 있습니다.
  • 파일 시스템 감시: 파일 변경 이벤트를 감지하여 자동 백업 또는 알림 기능을 구현할 수 있습니다.
  • API 서버: 요청 수신 이벤트를 처리하여 각 요청에 대한 적절한 응답을 제공할 수 있습니다.
  • 비동기 작업 관리: 여러 비동기 작업의 완료 이벤트를 처리하여 작업 순서를 제어하고 결과를 효율적으로 관리할 수 있습니다.

4. 스트림 모듈: 효율적인 데이터 처리

스트림 모듈은 Node.js에서 대용량 데이터를 효율적으로 처리하도록 설계된 핵심 모듈입니다. 데이터를 작은 조각(chunk)으로 나누어 순차적으로 처리하기 때문에, 메모리 사용량을 최소화하고 성능을 향상시킬 수 있습니다.

4.1 스트림: 데이터의 흐름

스트림은 데이터가 흐르는 추상적인 경로를 의미합니다. 마치 물이 파이프를 통해 흐르듯이, 데이터는 스트림을 통해 이동하며 처리됩니다.

주요 스트림 유형:

  1. 읽기 스트림 (Readable Stream): 데이터를 읽어오는 스트림입니다.
  2. 쓰기 스트림 (Writable Stream): 데이터를 쓰는 스트림입니다.
  3. 변환 스트림 (Transform Stream): 데이터를 읽고 변환하여 쓰는 스트림입니다.
  4. 듀플렉스 스트림 (Duplex Stream): 읽기와 쓰기가 모두 가능한 스트림입니다.

4.2 스트림의 핵심 장점

  • 메모리 효율성: 대용량 데이터를 작은 조각으로 나누어 처리하기 때문에 메모리 사용량을 크게 줄일 수 있습니다.
  • 비동기 처리: 스트림은 비동기적으로 작동하여, 데이터 처리 중에도 다른 작업을 수행할 수 있습니다. 이를 통해 애플리케이션의 응답성을 향상시킵니다.
  • 파이프라인: 여러 스트림을 연결(pipe)하여 데이터를 효율적으로 처리할 수 있습니다.

4.3 스트림 활용 예제

예제 1: 파일 읽기와 쓰기: 파이프를 이용한 효율적인 복사

const fs = require('fs');

// 읽기 스트림 생성 (input.txt)
const readStream = fs.createReadStream('input.txt', 'utf8');

// 쓰기 스트림 생성 (output.txt)
const writeStream = fs.createWriteStream('output.txt');

// 파이프를 사용하여 읽기 스트림을 쓰기 스트림에 연결
readStream.pipe(writeStream);

// 데이터 이벤트 처리 (선택 사항)
readStream.on('data', (chunk) => {
    console.log('읽은 데이터:', chunk);
});

// 완료 이벤트 처리
writeStream.on('finish', () => {
    console.log('파일 복사 완료!');
});

예제 2: HTTP 요청 데이터 처리: 스트림을 이용한 대용량 요청 처리

const http = require('http');

const server = http.createServer((req, res) => {
    let body = '';

    // 데이터 이벤트 처리: 요청 본문을 스트림으로 읽기
    req.on('data', (chunk) => {
        body += chunk.toString(); // 청크 단위로 데이터 누적
    });

    // 종료 이벤트 처리: 요청 처리가 완료되었을 때
    req.on('end', () => {
        console.log('받은 요청 본문:', body);
        res.end('요청 받음');
    });
});

server.listen(3000, () => {
    console.log('서버가 3000번 포트에서 시작되었습니다.');
});

예제 3: 변환 스트림: 데이터 변환 및 가공

const { Transform } = require('stream');

// 대문자로 변환하는 변환 스트림 생성
const toUpperCaseTransform = new Transform({
    transform(chunk, encoding, callback) {
        this.push(chunk.toString().toUpperCase());
        callback();
    }
});

// 표준 입력 스트림을 대문자 변환 스트림에 연결하고, 결과를 표준 출력 스트림으로 전달
process.stdin.pipe(toUpperCaseTransform).pipe(process.stdout);

예제 4: 파일 압축 (zlib 사용)

const fs = require('fs');
const zlib = require('zlib');

const gzip = zlib.createGzip(); // gzip 압축 스트림 생성
const readStream = fs.createReadStream('input.txt');
const writeStream = fs.createWriteStream('input.txt.gz');

readStream.pipe(gzip).pipe(writeStream); // input.txt를 압축하여 input.txt.gz로 저장

writeStream.on('finish', () => {
    console.log('파일 압축 완료!');
});

예제 5: 파일 복호화 (crypto 사용)

const fs = require('fs');
const crypto = require('crypto');

const algorithm = 'aes-256-cbc';
const password = 'your-secret-password';
const key = crypto.scryptSync(password, 'salt', 32);
const iv = crypto.randomBytes(16); // Initialization Vector

const decipher = crypto.createDecipheriv(algorithm, key, iv); // 복호화 스트림 생성
const readStream = fs.createReadStream('encrypted.dat');
const writeStream = fs.createWriteStream('decrypted.txt');

readStream.pipe(decipher).pipe(writeStream); // encrypted.dat를 복호화하여 decrypted.txt로 저장

writeStream.on('finish', () => {
    console.log('파일 복호화 완료!');
});

예제 6: CSV 파싱 (csv-parser 사용)

const fs = require('fs');
const csv = require('csv-parser');

const results = [];

fs.createReadStream('data.csv')
    .pipe(csv()) // CSV 파싱 스트림
    .on('data', (data) => results.push(data)) // 각 행을 객체로 변환하여 배열에 추가
    .on('end', () => {
        console.log('CSV 파싱 결과:', results);
    });

결론: Node.js 핵심 모듈 정복을 통한 성장

지금까지 Node.js의 핵심 모듈인 HTTP, 파일 시스템, 이벤트, 그리고 스트림 모듈에 대해 자세히 살펴보았습니다. 각 모듈의 개념, 주요 기능, 그리고 다양하고 실용적인 예제를 통해 Node.js의 강력한 기능을 이해하고 활용하는 방법을 익혔습니다.

728x90