프로그래밍/Node.js

Node.js 모듈 시스템 완벽 가이드: 핵심 개념부터 활용까지

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

1. 모듈 시스템의 기본 이해

1.1. 모듈이란 무엇인가?

모듈은 특정 기능이나 목적을 수행하는 독립적인 코드 블록입니다. Node.js에서 각 파일은 기본적으로 하나의 모듈로 간주되며, 다음과 같은 중요한 역할을 수행합니다.

  • 코드 재사용성 향상: 한 번 작성한 코드를 여러 곳에서 재사용하여 개발 효율성을 높입니다.
  • 유지보수 용이성 확보: 특정 기능 수정 시 해당 모듈만 변경하여 코드 유지보수를 편리하게 합니다.
  • 네임스페이스 관리: 전역 변수 사용을 줄여 변수 이름 충돌 가능성을 최소화합니다.
  • 코드 구조화: 코드를 논리적인 단위로 분리하여 복잡한 코드를 관리하기 쉽게 만듭니다.
  • 협업 효율성 증대: 모듈 단위로 개발하여 여러 개발자가 동시에 작업하는 데 도움을 줍니다.

1.2. Node.js 모듈 시스템의 특징

Node.js는 CommonJS 규격을 따르는 모듈 시스템을 사용하며, 주요 특징은 다음과 같습니다.

  • require() 함수: 다른 파일에서 정의된 모듈을 불러오는 데 사용합니다.
  • module.exports 객체: 모듈에서 외부로 공개할 변수, 함수, 객체 등을 정의할 때 사용합니다.
  • 모듈 캐싱: require()로 불러온 모듈은 캐싱되어, 동일한 모듈을 재사용할 때 성능을 향상시킵니다.
  • 순환 참조 주의: 모듈 간의 순환 참조는 오류를 유발할 수 있으므로 주의해야 합니다.

1.3. 모듈의 종류

Node.js 모듈은 다음과 같이 세 가지 유형으로 나눌 수 있습니다.

  • 내장 모듈 (Built-in Modules): Node.js 설치 시 기본적으로 제공되는 모듈
  • 외부 모듈 (External Modules): 다른 개발자들이 만든 패키지로, npm을 통해 설치
  • 사용자 정의 모듈 (User-defined Modules): 개발자가 직접 만든 모듈

2. 내장 모듈 (Built-in Modules): Node.js의 기본 기능

2.1. 내장 모듈이란?

Node.js는 다양한 기능을 미리 구현해 놓은 내장 모듈을 제공합니다. 이러한 모듈은 Node.js 설치 시 함께 제공되므로, 별도의 설치 과정 없이 바로 사용할 수 있습니다.

2.2. 주요 내장 모듈 소개 및 활용 예제

1. fs (File System): 파일 시스템 조작

  • 파일 및 디렉토리 생성, 읽기, 쓰기, 삭제 등 파일 시스템과 관련된 작업을 처리합니다.
const fs = require('fs');

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

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

// 파일 쓰기 (비동기)
const content = '새로운 파일 내용입니다.\n';
fs.writeFile('new_file.txt', content, (err) => {
    if (err) {
        console.error("파일 쓰기 오류:", err);
        return;
    }
    console.log("파일이 성공적으로 저장되었습니다.");

    // 파일 추가 쓰기 (append)
    fs.appendFile('new_file.txt', ' 추가된 내용', err => {
      if(err){
        console.error("파일 추가 쓰기 오류:", err);
        return;
      }
      console.log("파일에 추가 성공");
    })
});

// 디렉토리 생성
fs.mkdir('./new_directory', { recursive: true }, (err) => {
  if(err){
    console.error("디렉토리 생성 오류:", err);
    return;
  }
  console.log("디렉토리 생성 성공");
});

// 파일 정보 가져오기
fs.stat('example.txt', (err, stats) => {
  if(err){
      console.error("파일 정보 가져오기 오류", err);
      return;
  }
  console.log('파일 정보:', stats);
});

2. http: HTTP 서버 및 클라이언트

  • HTTP 서버를 만들거나 HTTP 요청을 보내는 데 필요한 기능을 제공합니다.
const http = require('http');
const url = require('url'); // URL 파싱을 위한 내장 모듈

const server = http.createServer((req, res) => {
    const parsedUrl = url.parse(req.url, true); // URL 파싱
    console.log("요청 URL:", parsedUrl.pathname);

    if(req.url === '/') {
        res.statusCode = 200;
        res.setHeader('Content-Type', 'text/plain');
        res.end('Node.js 메인 페이지 입니다.\n');
    } else if(req.url === '/about'){
         res.statusCode = 200;
        res.setHeader('Content-Type', 'text/html');
        res.end('<h1>About 페이지</h1><p>소개 페이지입니다.</p>');
    }
    else{
         res.statusCode = 404;
        res.setHeader('Content-Type', 'text/plain');
        res.end('404 Not found\n');
    }
});

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

3. path: 파일 경로 처리

  • 파일 경로를 조작하기 위한 유틸리티 함수를 제공합니다. 경로 결합, 분리, 정규화 등을 수행할 수 있습니다.
const path = require('path');

const filePath = '/user/local/bin/example.txt';

// 경로의 기본 이름 가져오기
const fileName = path.basename(filePath);
console.log('파일 이름:', fileName); // example.txt

// 경로의 확장자 가져오기
const fileExt = path.extname(filePath);
console.log("확장자:", fileExt); // .txt

// 경로의 디렉토리 부분 가져오기
const dirName = path.dirname(filePath);
console.log('디렉토리:', dirName); // /user/local/bin

// 경로 결합하기
const newPath = path.join('/user/files', 'new_file.txt');
console.log('결합된 경로:', newPath); // /user/files/new_file.txt

// 경로 정규화하기
const normalizedPath = path.normalize('/user//local/../bin/example.txt');
console.log('정규화된 경로:', normalizedPath); // /user/bin/example.txt

// 절대 경로 여부 확인하기
console.log("절대 경로:", path.isAbsolute(filePath)); // true
console.log("상대 경로:", path.isAbsolute('relative/path')); // false

// 경로 분리하기
const pathParsed = path.parse(filePath);
console.log("경로 분석:",pathParsed);
// 출력
// {
//   root: '/',
//   dir: '/user/local/bin',
//   base: 'example.txt',
//   ext: '.txt',
//   name: 'example'
// }

4. os: 운영 체제 정보

  • 운영 체제와 관련된 정보를 제공합니다. 플랫폼, 아키텍처, 메모리 정보 등을 확인할 수 있습니다.
const os = require('os');

console.log('운영체제:', os.platform()); // 예: darwin, linux, win32
console.log('CPU 아키텍처:', os.arch()); // 예: x64, arm64
console.log('사용 가능한 메모리:', os.freemem());
console.log('전체 메모리:', os.totalmem());
console.log('CPU 정보:', os.cpus());
console.log('네트워크 인터페이스:', os.networkInterfaces());
console.log('사용자 홈 디렉토리:', os.homedir());
console.log('일시 디렉토리:', os.tmpdir());
console.log('엔디안:', os.endianness());
console.log('운영체제 버전:', os.version());
console.log("시스템 가동 시간(초):",os.uptime());

5. events: 이벤트 기반 프로그래밍

  • 이벤트 기반 프로그래밍을 위한 기능을 제공합니다. 사용자 정의 이벤트를 생성하고 처리할 수 있습니다.
const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter();

// 'customEvent' 이벤트 리스너 설정
myEmitter.on('customEvent', (arg1, arg2) => {
    console.log('이벤트 발생!:', arg1, arg2);
});

myEmitter.on('error',(err) => {
  console.error("에러 발생:",err)
})

// 'customEvent' 이벤트 발생시키기
myEmitter.emit('customEvent', '첫 번째 인자', '두 번째 인자');

// 에러 이벤트 발생
myEmitter.emit('error',new Error("테스트 에러"));

// 한번만 실행되는 이벤트
myEmitter.once('onceEvent', ()=>{
  console.log("한번만 실행!");
});

myEmitter.emit("onceEvent");
myEmitter.emit("onceEvent");

6. zlib: 압축 및 해제

  • 파일 압축 및 해제 기능을 제공합니다.
const zlib = require('zlib');
const fs = require('fs');

// 파일 압축
const gzip = zlib.createGzip();
const inputFile = fs.createReadStream('example.txt');
const outputFile = fs.createWriteStream('example.txt.gz');

inputFile.pipe(gzip).pipe(outputFile);

console.log('파일 압축 완료');

// 파일 압축 해제
const gunzip = zlib.createGunzip();
const compressedFile = fs.createReadStream('example.txt.gz');
const decompressedFile = fs.createWriteStream('example_decompressed.txt');

compressedFile.pipe(gunzip).pipe(decompressedFile);
console.log('파일 압축 해제 완료');

7. crypto: 암호화

  • 암호화 기능을 제공합니다.
const crypto = require('crypto');

// 해시 생성
const hash = crypto.createHash('sha256');
hash.update('암호화할 내용');
const hashedValue = hash.digest('hex');
console.log('해시 값:', hashedValue);

// 암호화
const secretKey = crypto.randomBytes(32);
const iv = crypto.randomBytes(16);

function encrypt(text, secretKey, iv){
    const cipher = crypto.createCipheriv('aes-256-cbc', secretKey, iv);
    let encrypted = cipher.update(text, 'utf8', 'hex');
    encrypted += cipher.final('hex');
    return encrypted;
}

const textToEncrypt = "암호화할 텍스트 입니다."
const encryptedText = encrypt(textToEncrypt, secretKey, iv);
console.log("암호화된 텍스트:",encryptedText);

// 복호화
function decrypt(encrypted, secretKey, iv){
  const decipher = crypto.createDecipheriv('aes-256-cbc', secretKey, iv);
  let decrypted = decipher.update(encrypted, 'hex', 'utf8');
  decrypted += decipher.final('utf8');
  return decrypted;
}

const decryptedText = decrypt(encryptedText, secretKey, iv);
console.log("복호화된 텍스트:", decryptedText);

// 난수 생성
const randomBytes = crypto.randomBytes(16);
console.log('난수:', randomBytes.toString('hex'));

3. 외부 모듈 (External Modules): npm을 통한 기능 확장

3.1. 외부 모듈이란?

외부 모듈은 다른 개발자들이 Node.js 생태계에서 만들어 배포한 패키지입니다. 이러한 패키지는 특정 기능이나 라이브러리를 제공하며, npm(Node Package Manager)을 통해 쉽게 설치하고 사용할 수 있습니다.

3.2. npm (Node Package Manager)

npm은 Node.js의 기본 패키지 관리자입니다. 외부 모듈을 설치, 관리, 삭제하는 데 사용됩니다.

설치 방법:

npm install <패키지_이름>

예시: lodash라는 유틸리티 라이브러리 설치 예시:

npm install lodash

nodemon이라는 파일 변경 감지 및 서버 재시작 모듈 설치 예시:

npm install -g nodemon

3.3. 주요 외부 모듈 소개 및 활용 예제

1. lodash: 유틸리티 라이브러리

  • 배열, 객체 등 다양한 데이터 구조를 다루기 위한 유틸리티 함수를 제공합니다.
const _ = require('lodash');

const array = [1, 2, 2, 3, 4, 4, 5];
const uniqueArray = _.uniq(array);
console.log('중복 제거된 배열:', uniqueArray);

const users = [
    { name: 'Alice', age: 25 },
    { name: 'Bob', age: 30 },
    { name: 'Alice', age: 25 }
];
const uniqueUsers = _.uniqWith(users, _.isEqual);
console.log("중복 제거된 객체 배열:",uniqueUsers);

const numbers = [1,2,3,4,5];
const sum = _.sum(numbers);
console.log("배열 합:", sum);

const obj1 = { a: 1, b: { c: 2 } };
const obj2 = { d: 3, b: { c: 4 } };
const mergedObj = _.merge(obj1, obj2);
console.log("합쳐진 객체:",mergedObj);

const clonedObj = _.cloneDeep(mergedObj);
console.log("깊은 복사:", clonedObj);
console.log("깊은 복사 참조 비교:", clonedObj === mergedObj);

2. express: 웹 프레임워크

  • 웹 서버 및 API를 구축하기 위한 프레임워크입니다.
const express = require('express');
const app = express();
const PORT = 3000;

// JSON 요청 처리 미들웨어
app.use(express.json());

app.get('/', (req, res) => {
    res.send('Express 서버에 연결되었습니다! 메인 페이지 입니다.');
});

app.get('/users/:id', (req, res)=>{
    const userId = req.params.id;
    res.send(`유저 ID: ${userId} 에 대한 정보를 보여줍니다.`)
})

// POST 요청 처리
app.post('/login', (req, res) => {
    const { username, password } = req.body;
    if (username === 'test' && password === '1234') {
        res.send('로그인 성공!');
    } else {
        res.status(401).send('로그인 실패.');
    }
});

// 정적 파일 제공
app.use('/static', express.static('public'));
// public 폴더 내에 index.html 파일을 생성후 http://localhost:3000/static/index.html 로 접속 가능

app.listen(PORT, () => {
    console.log(`Express 서버가 http://localhost:${PORT} 에서 실행 중입니다.`);
});

3. axios: HTTP 클라이언트

  • HTTP 요청을 보내기 위한 Promise 기반 클라이언트입니다.
const axios = require('axios');

// GET 요청
axios.get('https://jsonplaceholder.typicode.com/posts/1')
    .then(response => {
        console.log('GET 응답:', response.data);
    })
    .catch(error => {
        console.error('GET 요청 오류:', error);
    });
// POST 요청
const postData = {
    title: '새로운 게시물',
    body: '게시물 내용입니다.',
    userId: 1
};
axios.post('https://jsonplaceholder.typicode.com/posts', postData)
  .then(response => {
      console.log('POST 응답:', response.data);
    })
    .catch(error => {
      console.error('POST 요청 오류:', error);
    });

// 파라미터 추가 요청
axios.get('https://jsonplaceholder.typicode.com/comments',{
    params:{
        postId:1
    }
})
  .then(response => {
      console.log("댓글 데이터:", response.data);
    })
    .catch(error => {
      console.error("파라미터 추가 요청 오류:", error);
    });

// 에러 처리
axios.get('https://jsonplaceholder.typicode.com/posts/invalid_id')
  .then(response => {
      console.log("응답 성공", response.data);
    })
  .catch(error => {
        if (error.response) {
            // 서버에서 응답 코드가 있는 에러
            console.error("에러 응답 데이터:", error.response.data);
            console.error("에러 응답 코드:", error.response.status);
            console.error("에러 응답 헤더:", error.response.headers);
        } else if(error.request){
              // 요청이 전송되지 않은 경우
            console.error("요청 오류:", error.request);
        }
        else{
            // 그 외 오류
             console.error("일반 오류:", error.message);
        }
    });

4. dotenv: 환경 변수 관리

  • 환경 변수를 관리하는 모듈입니다.
npm install dotenv

.env 파일 생성

API_KEY=your_api_key
DATABASE_URL=your_database_url
PORT=5000
require('dotenv').config();

const apiKey = process.env.API_KEY;
const databaseURL = process.env.DATABASE_URL;
const port = process.env.PORT;

console.log("API 키:",apiKey);
console.log("데이터베이스 URL:",databaseURL);
console.log("포트:",port);

5. morgan: HTTP 요청 로깅 미들웨어

  • HTTP 요청 로그를 출력하는 미들웨어 입니다.
    npm install morgan
const express = require('express');
const morgan = require('morgan');

const app = express();
app.use(morgan('dev')); // 개발 환경에서 로그 출력

app.get('/', (req, res) => {
    res.send('Hello');
})

app.listen(3000, () => {
    console.log('Server is running on port 3000');
});

4. 사용자 정의 모듈 (User-defined Modules): 직접 모듈 만들기

4.1. 사용자 정의 모듈이란?

사용자 정의 모듈은 개발자가 직접 만들어 사용하는 모듈입니다. 특정 기능이나 로직을 모듈화하여 재사용성을 높이고 코드 구조를 개선할 수 있습니다.

4.2. 사용자 정의 모듈 생성 및 사용 방법

1. 모듈 생성하기:
새로운 JavaScript 파일을 만들고, 필요한 기능이나 변수를 정의한 후 module.exports를 통해 외부로 노출시킵니다.

  • math.js 파일 생성:
// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
const multiply = (a,b) => a * b;
const divide = (a,b) => {
    if(b===0){
        throw new Error("0으로 나눌 수 없습니다.");
    }
    return a / b;
}
const PI = 3.141592;

// 모듈 외부로 공개 (객체로 내보내기)
module.exports = {
    add,
    subtract,
    multiply,
    divide,
    PI
};
  • string_utils.js 파일 생성:
    // string_utils.js
    const reverseString = (str) => {
    return str.split('').reverse().join('');
    }
    

const capitalizeString = (str) => {
return str.charAt(0).toUpperCase() + str.slice(1);
}

// 모듈 외부로 공개 (함수만 내보내기)
module.exports = {
reverseString,
capitalizeString
}

**2. 모듈 사용하기:**
`require()` 함수를 사용하여 생성한 모듈을 불러와서 사용합니다.

*   `app.js` 파일 생성:
```javascript
// app.js
const math = require('./math'); // math.js 모듈 가져오기
const stringUtils = require('./string_utils'); // string_utils.js 모듈 가져오기

console.log('덧셈 결과:', math.add(5, 3));
console.log('뺄셈 결과:', math.subtract(5, 3));
console.log('곱셈 결과:', math.multiply(5,3));
console.log("나눗셈 결과:", math.divide(10,2));
try{
    console.log("나눗셈 결과(에러):", math.divide(10,0));
}catch(err){
    console.log("나눗셈 오류:", err.message)
}
console.log("PI 값:", math.PI);

console.log("문자열 뒤집기:",stringUtils.reverseString("hello"));
console.log("문자열 대문자 변경:", stringUtils.capitalizeString("world"));

5. 모듈 시스템 활용 팁

  • 모듈화: 코드를 기능별로 분리하여 모듈화하면 유지보수 및 재사용성이 향상됩니다.
  • npm 활용: 오픈 소스 생태계에서 제공하는 다양한 외부 모듈을 적극적으로 활용하여 개발 시간을 단축시키고 코드 품질을 높일 수 있습니다.
  • 모듈 이름 규칙: 모듈 이름은 명확하고 의미 있게 지어 다른 개발자들이 쉽게 이해하고 사용할 수 있도록 합니다.
  • package.json: 프로젝트에 필요한 외부 모듈 목록을 관리하고, 프로젝트를 쉽게 공유할 수 있도록 합니다.
  • 모듈 패턴 활용: 모듈을 효과적으로 관리하기 위해 모듈 패턴(예: Revealing Module Pattern)을 사용해 보세요.
  • 순환 참조 방지: 모듈 간의 순환 참조는 피해야 합니다. 순환 참조는 프로그램 실행 중 오류를 유발할 수 있습니다.

6. 결론

Node.js의 모듈 시스템은 개발 생산성을 높이고, 프로젝트를 구조적으로 관리하는 데 필수적인 요소입니다. 내장 모듈을 통해 기본적인 기능을 쉽게 구현하고, 외부 모듈을 활용하여 더욱 풍부하고 강력한 기능을 추가할 수 있습니다. 또한 사용자 정의 모듈을 통해 코드의 재사용성과 유지보수성을 높여 효율적인 개발이 가능합니다.

이 포스트를 통해 Node.js 모듈 시스템에 대한 깊은 이해를 얻으셨기를 바랍니다. 다양한 예제를 직접 실행해보고, 실제 프로젝트에 적용해보면서 더욱 숙련된 Node.js 개발자로 성장하시기를 응원합니다.

728x90