프로그래밍/Node.js

Node.js 모듈 시스템 정복 가이드: 개념부터 활용까지

shimdh 2025. 2. 19. 00:13
728x90

1. 모듈: 코드 구성의 기본 단위

1.1 모듈이란 무엇인가?

모듈은 특정 기능이나 데이터를 캡슐화한 독립적인 코드 블록입니다. 쉽게 말해, 하나의 잘 정의된 역할을 수행하는 코드 덩어리라고 생각할 수 있습니다. Node.js에서 각 파일은 개별 모듈로 취급됩니다. 모듈 시스템은 이러한 코드 블록을 효율적으로 구성하고 재사용할 수 있도록 돕는 체계입니다. 이를 통해 개발자는 필요한 기능만 선택적으로 가져다 사용할 수 있어 코드 가독성이 향상되고 유지보수가 쉬워집니다.

2. 모듈 활용의 핵심: requiremodule.exports

Node.js에서 모듈을 사용하기 위해서는 module.exports를 통해 모듈을 외부로 공개(내보내기)하고, require를 통해 필요한 모듈을 가져와야 합니다.

2.1 모듈 내보내기: module.exports

module.exports는 모듈의 기능을 다른 파일에서 사용할 수 있도록 외부로 공개하는 역할을 합니다. 특정 함수, 객체, 변수 등을 module.exports에 할당하여 외부에서 접근 가능하게 만들 수 있습니다.

예제 1: math.js - 수학 관련 함수들을 모듈로 내보내기

// math.js

function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

function multiply(a, b) {
  return a * b;
}

function divide(a,b) { // 나누기 함수 추가
  return a / b;
}

// add, subtract, multiply, divide 함수를 외부에서 사용할 수 있도록 객체 형태로 내보냅니다.
module.exports = {
    add,
    subtract,
    multiply,
    divide // 추가
};

예제 2: greetings.js - 다양한 인사말을 제공하는 모듈

// greetings.js

const koreanGreeting = (name) => `안녕하세요, ${name}님!`;
const englishGreeting = (name) => `Hello, ${name}!`;

// korean, english 인사말 함수를 외부에서 사용할 수 있도록 객체 형태로 내보냅니다.
module.exports = {
    korean: koreanGreeting,
    english: englishGreeting
};

2.2 모듈 가져오기: require

require 함수는 다른 모듈을 현재 파일로 불러와 사용할 수 있게 해줍니다. require 함수에 가져올 모듈의 경로를 인자로 전달하면 해당 모듈에서 module.exports로 공개한 객체를 반환합니다.

예제 1: app.js - math.js 모듈 사용하기

// app.js

// math.js 모듈을 가져옵니다.
const math = require('./math');

console.log(math.add(5, 3));        // 출력: 8
console.log(math.subtract(10, 4));   // 출력: 6
console.log(math.multiply(2, 6));   // 출력: 12
console.log(math.divide(10,2)); // 출력 : 5

예제 2: main.js - greetings.js 모듈 사용하기

// main.js

// greetings.js 모듈을 가져옵니다.
const greetings = require('./greetings');

console.log(greetings.korean('철수')); // 출력: 안녕하세요, 철수님!
console.log(greetings.english('John')); // 출력: Hello, John!

예제 3: calculator.js - 더하기, 곱하기 기능을 제공하는 모듈

// calculator.js

// 더하기, 곱하기 함수를 각각 내보냅니다.
module.exports.add = (a, b) => a + b;
module.exports.multiply = (a, b) => a * b;

예제 4: index.js - calculator.js 모듈 사용하기

// index.js

// calculator.js 모듈을 가져옵니다.
const calculator = require('./calculator');

console.log(calculator.add(10, 5));      // 출력: 15
console.log(calculator.multiply(3, 7));   // 출력: 21

3. Node.js 내장 모듈: 강력하고 편리한 기본 도구

Node.js는 별도의 설치 없이 바로 사용할 수 있는 다양한 내장 모듈을 기본적으로 제공합니다. 이러한 내장 모듈은 파일 시스템 접근, HTTP 서버 구축, 경로 처리, 운영 체제 정보 획득 등 개발에 자주 사용되는 핵심 기능들을 포함하고 있어 매우 유용합니다.

3.1 파일 시스템 제어: fs 모듈

fs (File System) 모듈은 파일 및 디렉터리를 생성, 읽기, 수정, 삭제하는 등 파일 시스템과 관련된 작업을 위한 API를 제공합니다.

예제: 파일 읽기, 쓰기, 디렉토리 생성, 파일 삭제, 파일 존재 여부 확인

const fs = require('fs');

// 1. example.txt 파일을 비동기적으로 읽어서 콘솔에 출력합니다.
fs.readFile('example.txt', 'utf8', (err, data) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('파일 내용:', data);
});

// 2. output.txt 파일에 "Hello, Node.js!" 문자열을 비동기적으로 씁니다.
fs.writeFile('output.txt', 'Hello, Node.js!', err => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('파일 쓰기 완료');
});

// 3. new-directory 디렉토리를 비동기적으로 생성합니다.
fs.mkdir('new-directory', (err) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('디렉토리 생성 완료');
});

// 4. file-to-delete.txt 파일을 비동기적으로 삭제합니다.
fs.unlink('file-to-delete.txt', (err) => {
    if (err) {
        console.error(err);
        return;
    }
    console.log('파일 삭제 완료');
});

// 5. example.txt 파일의 존재 여부를 확인합니다.
fs.access('example.txt', fs.constants.F_OK, (err) => {
    console.log(`example.txt ${err ? '존재하지 않음' : '존재함'}`);
});

주요 함수 설명:

  • fs.readFile(path, options, callback): 파일을 비동기적으로 읽습니다.
    • path: 파일 경로
    • options: 인코딩 등의 옵션 (예: 'utf8')
    • callback: 파일 읽기가 완료된 후 호출되는 콜백 함수. 에러(err)와 파일 내용(data)을 인자로 받습니다.
  • fs.writeFile(file, data, options, callback): 파일에 비동기적으로 데이터를 씁니다.
    • file: 파일 경로
    • data: 파일에 쓸 데이터
    • options: 인코딩 등의 옵션
    • callback: 파일 쓰기가 완료된 후 호출되는 콜백 함수. 에러(err)를 인자로 받습니다.
  • fs.mkdir(path[, options], callback): 새로운 디렉터리를 비동기적으로 생성합니다.
    • path: 생성할 디렉터리 경로
    • options: 디렉터리 생성 옵션 (예: 권한 설정)
    • callback: 디렉터리 생성 완료 후 호출되는 콜백 함수. 에러(err)를 인자로 받습니다.
  • fs.unlink(path, callback): 파일을 비동기적으로 삭제합니다.
    • path: 삭제할 파일 경로
    • callback: 파일 삭제 완료 후 호출되는 콜백 함수. 에러(err)를 인자로 받습니다.
  • fs.access(path, mode, callback): 파일의 존재 여부 및 접근 권한을 확인합니다.
    • path: 확인할 파일 경로
    • mode: 접근 권한 확인 모드 (예: fs.constants.F_OK - 파일 존재 여부)
    • callback: 확인 완료 후 호출되는 콜백 함수. 에러 발생 시 에러(err)를 인자로 받습니다.

3.2 HTTP 서버 구축: http 모듈

http 모듈은 HTTP 서버와 클라이언트를 구축하는 데 필요한 API를 제공합니다. 이를 통해 웹 서버를 손쉽게 개발할 수 있습니다.

예제: HTTP 서버 생성, HTML 응답, JSON 응답, URL에 따른 동적 응답

const http = require('http');

// 1. 3000번 포트에서 "안녕하세요! 이것은 HTTP 서버입니다."라는 텍스트 응답을 보내는 기본 HTTP 서버를 생성합니다.
const server = http.createServer((req, res) => {
    res.statusCode = 200;
    res.setHeader('Content-Type', 'text/plain');
    res.end('안녕하세요! 이것은 HTTP 서버입니다.');
});

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

// 2. 3001번 포트에서 HTML 형식의 응답을 보내는 서버를 생성합니다.
const htmlServer = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.end('<h1>Hello, World!</h1><p>This is an HTML response.</p>');
});

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

// 3. 3002번 포트에서 JSON 형식의 응답을 보내는 서버를 생성합니다.
const jsonServer = http.createServer((req, res) => {
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ message: 'This is a JSON response.' }));
});

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

// 4. 3003번 포트에서 요청 URL에 따라 다른 응답을 보내는 동적인 서버를 생성합니다.
const dynamicServer = http.createServer((req, res) => {
    if (req.url === '/hello') {
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.end('Hello, user!');
    } else if (req.url === '/goodbye') {
        res.writeHead(200, { 'Content-Type': 'text/plain' });
        res.end('Goodbye, user!');
    } else {
        res.writeHead(404, { 'Content-Type': 'text/plain' });
        res.end('Not Found');
    }
});

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

주요 함수 설명:

  • http.createServer([options][, requestListener]): HTTP 서버 객체를 생성합니다.
    • requestListener: 클라이언트의 요청이 있을 때마다 호출되는 함수. request (요청 객체)와 response (응답 객체)를 인자로 받습니다.
  • server.listen(port, callback): 서버를 지정된 포트에서 실행합니다.
    • port: 서버가 실행될 포트 번호
    • callback: 서버가 시작된 후 호출되는 콜백 함수

3.3 파일 경로 처리: path 모듈

path 모듈은 파일 경로를 안전하고 효율적으로 처리하기 위한 유틸리티 함수들을 제공합니다. 특히, 운영체제별로 다른 경로 구분자(separator)를 고려하여 플랫폼 간 호환성을 높여줍니다.

예제: 파일 경로 결합, 파일 이름/디렉터리 이름/확장자 추출, 경로 정규화, 경로 구분자 확인

const path = require('path');

// 1. 현재 디렉토리(__dirname)와 'folder', 'file.txt'를 결합하여 플랫폼에 맞는 전체 경로를 생성합니다.
const fullPath = path.join(__dirname, 'folder', 'file.txt');
console.log('전체 경로:', fullPath); // 예: /Users/username/project/folder/file.txt (운영체제에 따라 다를 수 있음)

// 2. /Users/username/project/folder/file.txt 경로에서 파일 이름(file.txt)을 추출합니다.
const filename = path.basename('/Users/username/project/folder/file.txt');
console.log('파일 이름:', filename);

// 3. /Users/username/project/folder/file.txt 경로에서 디렉터리 이름(/Users/username/project/folder)을 추출합니다.
const dirname = path.dirname('/Users/username/project/folder/file.txt');
console.log('디렉터리 이름:', dirname);

// 4. /Users/username/project/folder/file.txt 경로에서 확장자(.txt)를 추출합니다.
const extname = path.extname('/Users/username/project/folder/file.txt');
console.log('확장자:', extname);

// 5. /Users/username/../project/folder/./file.txt 경로를 정규화합니다. (불필요한 . 및 .. 제거)
const normalizedPath = path.normalize('/Users/username/../project/folder/./file.txt');
console.log('정규화된 경로:', normalizedPath); // 예: /Users/project/folder/file.txt (운영체제에 따라 다를 수 있음)

// 6. 현재 운영체제의 경로 구분자를 출력합니다.
console.log('경로 구분자:', path.sep); // 예: / (macOS, Linux), \ (Windows)

주요 함수 설명:

  • path.join([...paths]): 여러 경로 문자열을 하나로 결합하여 플랫폼에 맞는 경로를 생성합니다.
  • path.basename(path[, suffix]): 경로의 마지막 부분(파일 이름)을 반환합니다.
    • path: 전체 경로
    • suffix: (선택적) 제거할 확장자
  • path.dirname(path): 경로에서 디렉터리 이름 부분을 반환합니다.
    • path: 전체 경로
  • path.extname(path): 경로에서 파일 확장자를 반환합니다.
    • path: 전체 경로
  • path.normalize(path): 경로 문자열을 정규화하여 불필요한 ... 부분을 정리합니다.
    • path: 정규화할 경로
  • path.sep: 플랫폼별 경로 구분자를 제공합니다. (Windows는 \, POSIX는 /)

3.4 운영체제 정보: os 모듈

os 모듈은 운영체제와 관련된 정보를 제공하는 유틸리티 함수들을 포함하고 있습니다. 이를 통해 시스템의 CPU, 메모리, 플랫폼, 아키텍처 등의 정보를 얻을 수 있습니다.

예제: 운영체제 이름, 호스트 이름, CPU 정보, 총 메모리, 사용 가능 메모리, 플랫폼, 아키텍처, 사용자 정보 조회

const os = require('os');

// 1. 운영체제 이름을 출력합니다.
console.log(`운영체제 이름: ${os.type()}`); // 예: Darwin (macOS), Windows_NT (Windows)

// 2. 호스트 이름을 출력합니다.
console.log(`호스트 이름: ${os.hostname()}`); // 예: my-computer.local

// 3. CPU 코어 정보를 배열로 출력합니다.
console.log('CPU 정보:', os.cpus());

// 4. 시스템의 총 메모리 양을 바이트 단위로 출력합니다.
console.log(`총 메모리: ${os.totalmem()} bytes`);

// 5. 시스템의 사용 가능한 메모리 양을 바이트 단위로 출력합니다.
console.log(`사용 가능한 메모리: ${os.freemem()} bytes`);

// 6. 운영체제 플랫폼을 식별하는 문자열을 출력합니다.
console.log(`플랫폼: ${os.platform()}`); // 예: darwin, win32, linux

// 7. 운영체제의 CPU 아키텍처를 식별하는 문자열을 출력합니다.
console.log(`아키텍처: ${os.arch()}`); // 예: x64, arm64

// 8. 현재 사용자에 대한 정보(사용자 ID, 그룹 ID, 홈 디렉터리 등)를 객체로 출력합니다.
console.log('사용자 정보:', os.userInfo());

주요 함수 설명:

  • os.type(): 운영체제 이름을 반환합니다.
  • os.hostname(): 호스트 이름을 반환합니다.
  • os.cpus(): CPU 코어 정보를 배열로 반환합니다.
  • os.totalmem(): 시스템의 총 메모리 양을 바이트 단위로 반환합니다.
  • os.freemem(): 시스템의 사용 가능한 메모리 양을 바이트 단위로 반환합니다.
  • os.platform(): 운영체제 플랫폼을 식별하는 문자열을 반환합니다. (예: 'darwin', 'win32', 'linux')
  • os.arch(): 운영체제의 CPU 아키텍처를 식별하는 문자열을 반환합니다. (예: 'x64', 'arm64')
  • os.userInfo([options]): 현재 사용자에 대한 정보를 담은 객체를 반환합니다.

4. 커스텀 모듈: 사용자 정의 모듈 생성 및 활용

Node.js의 내장 모듈이 제공하는 기능 외에도, 개발자가 직접 필요한 기능을 묶어 커스텀 모듈을 생성하여 활용할 수 있습니다. 커스텀 모듈은 코드 재사용성을 높이고 프로젝트 구조를 체계적으로 관리하는 데 큰 도움이 됩니다.

4.1 커스텀 모듈 생성 및 사용 절차

  1. 새 파일 생성: 커스텀 모듈로 만들 기능들을 포함할 새로운 JavaScript 파일을 생성합니다. (예: myModule.js)
  2. 기능 구현 및 내보내기: 파일에 필요한 기능을 구현하고, module.exports를 사용하여 외부에서 사용할 수 있도록 공개합니다.
  3. 모듈 가져오기 및 사용: 메인 애플리케이션 파일에서 require 함수를 사용하여 생성한 커스텀 모듈을 가져와서 사용합니다.

4.2 커스텀 모듈 예제

예제 1: greet.js - 인사말 및 작별 인사 생성 모듈

// greet.js

function greet(name) {
    return `안녕하세요, ${name}!`;
}

function farewell(name) {
    return `안녕히 가세요, ${name}!`;
}

// greet, farewell 함수를 외부에서 사용할 수 있도록 객체 형태로 내보냅니다.
module.exports = { greet, farewell };

예제 2: app.js - greet.js 모듈 사용

// app.js

// greet.js 모듈을 가져옵니다. 객체의 구조 분해 할당 문법을 사용합니다.
const { greet, farewell } = require('./greet');

console.log(greet('홍길동'));     // 출력: 안녕하세요, 홍길동!
console.log(farewell('김철수'));  // 출력: 안녕히 가세요, 김철수!

예제 3: logger.js - 로그 메시지 출력 모듈

// logger.js

function logInfo(message) {
    console.log(`[INFO] ${message}`);
}

function logError(message) {
    console.error(`[ERROR] ${message}`);
}

// logInfo, logError 함수를 외부에서 사용할 수 있도록 객체 형태로 내보냅니다.
module.exports = { logInfo, logError };

예제 4: main.js - logger.js 모듈 사용

// main.js

// logger.js 모듈을 가져옵니다.
const logger = require('./logger');

logger.logInfo('정보 메시지 출력');
logger.logError('에러 메시지 출력');

예제 5: arrayUtils.js - 배열 유틸리티 모듈

// arrayUtils.js

const sumArray = (arr) => arr.reduce((sum, num) => sum + num, 0);
const findMax = (arr) => Math.max(...arr);

// sumArray, findMax 함수를 외부에서 사용할 수 있도록 객체 형태로 내보냅니다.
module.exports = { sumArray, findMax };

예제 6: app.js - arrayUtils.js 모듈 사용

// app.js

// arrayUtils.js 모듈을 가져옵니다.
const arrayUtils = require('./arrayUtils');

const numbers = [1, 2, 3, 4, 5];
console.log('배열 합계:', arrayUtils.sumArray(numbers)); // 출력: 배열 합계: 15
console.log('최댓값:', arrayUtils.findMax(numbers));     // 출력: 최댓값: 5

4.3 폴더 구조를 활용한 모듈 관리

프로젝트 규모가 커지면 여러 개의 모듈이 생겨나게 됩니다. 이때, 관련 있는 모듈들을 하나의 폴더에 모아서 관리하면 프로젝트 구조를 더 체계적으로 유지할 수 있습니다. 예를 들어, utils 폴더를 생성하고 그 안에 다양한 유틸리티 모듈을 모아둘 수 있습니다.

예제: 프로젝트 폴더 구조

project/
├── utils/
│   ├── mathUtils.js
│   └── stringUtils.js
└── app.js

예제: mathUtils.js - 수학 관련 유틸리티 함수 모듈

// utils/mathUtils.js

function add(a, b) {
    return a + b;
}

function subtract(a, b) {
    return a - b;
}

function power(base, exponent) {
    return Math.pow(base, exponent);
}

// add, subtract, power 함수를 외부에서 사용할 수 있도록 객체 형태로 내보냅니다.
module.exports = { add, subtract, power };

예제: stringUtils.js - 문자열 관련 유틸리티 함수 모듈

// utils/stringUtils.js

function capitalize(str) {
    return str.charAt(0).toUpperCase() + str.slice(1);
}

function reverseString(str) {
    return str.split('').reverse().join('');
}

// capitalize, reverseString 함수를 외부에서 사용할 수 있도록 객체 형태로 내보냅니다.
module.exports = { capitalize, reverseString };

예제: app.js - utils 폴더 내 모듈 사용

// app.js

// utils 폴더 내의 mathUtils.js, stringUtils.js 모듈을 가져옵니다.
const mathUtils = require('./utils/mathUtils');
const stringUtils = require('./utils/stringUtils');

console.log('대문자 변환:', stringUtils.capitalize('hello'));          // 출력: 대문자 변환: Hello
console.log('문자열 뒤집기:', stringUtils.reverseString('world'));    // 출력: 문자열 뒤집기: dlrow

const sum = mathUtils.add(10, 20);
console.log(`합계: ${sum}`);                                        // 출력: 합계: 30
console.log('거듭제곱:', mathUtils.power(2, 3));                     // 출력: 거듭제곱: 8

5. 결론: Node.js 모듈 시스템, 강력한 도약을 위한 발판

Node.js의 모듈 시스템은 코드 구성과 재사용을 위한 강력하고 효율적인 체계를 제공합니다. requiremodule.exports를 통해 모듈을 가져오고 내보내는 핵심 메커니즘을 이해하고, 파일 시스템(fs), HTTP(http), 경로 처리(path), 운영체제 정보(os)와 같은 유용한 내장 모듈을 자유자재로 활용할 수 있어야 합니다. 또한, 프로젝트의 특성에 맞게 커스텀 모듈을 직접 생성하고 폴더 구조를 활용하여 체계적으로 관리하는 능력을 키운다면 Node.js 개발자로서 한 단계 더 성장할 수 있을 것입니다. 본 가이드가 Node.js 모듈 시스템을 정복하고, 더 효율적이고 유지보수 가능한 코드를 작성하는 데 든든한 기반이 되기를 바랍니다.

728x90