[2026] Node.js 모듈 시스템 | CommonJS와 ES Modules 완벽 가이드

[2026] Node.js 모듈 시스템 | CommonJS와 ES Modules 완벽 가이드

이 글의 핵심

Node.js 모듈 시스템: CommonJS와 ES Modules CommonJS 모듈·ES Modules.

들어가며

모듈이란?

모듈은 재사용 가능한 코드 조각을 파일로 분리한 것입니다. Node.js는 두 가지 모듈 시스템을 지원합니다:

  1. CommonJS: Node.js 기본 방식 (require, module.exports)
  2. ES Modules: JavaScript 표준 방식 (import, export) 한 프로젝트 안에서 도구를 주머니마다 나눠 담듯 파일을 나누면, 이름이 겹치지 않고(네임스페이스), 테스트와 교체가 쉬워집니다. require('express')처럼 이름만 말하면 node_modules에서 꺼내 오는 것은, 공용 서랍에서 표준 부품을 가져오는 것과 비슷합니다. npm·package-lock.json의존성 해석·재현 설치의 중심입니다. 같은 축에서 보면 Python pip·uv·Poetry·Go 모듈·go.sum·Rust Cargo가 있고, C++는 CMakeConan·vcpkg 조합이 자주 쓰입니다. 빌드 철학 비교는 C++ 빌드 시스템 완전 비교를 참고하세요. 모듈의 장점:
  • 코드 재사용: 한 번 작성, 여러 곳에서 사용
  • 네임스페이스: 전역 스코프 오염 방지
  • 유지보수: 기능별로 파일 분리
  • 의존성 관리: 명확한 의존 관계
  • 테스트: 독립적인 단위 테스트 가능

실무에서 마주한 현실

개발을 배울 때는 모든 게 깔끔하고 이론적입니다. 하지만 실무는 다릅니다. 레거시 코드와 씨름하고, 급한 일정에 쫓기고, 예상치 못한 버그와 마주합니다. 이 글에서 다루는 내용도 처음엔 이론으로 배웠지만, 실제 프로젝트에 적용하면서 “아, 이래서 이렇게 설계하는구나” 하고 깨달은 것들입니다. 특히 기억에 남는 건 첫 프로젝트에서 겪은 시행착오입니다. 책에서 배운 대로 했는데 왜 안 되는지 몰라 며칠을 헤맸죠. 결국 선배 개발자의 코드 리뷰를 통해 문제를 발견했고, 그 과정에서 많은 걸 배웠습니다. 이 글에서는 이론뿐 아니라 실전에서 마주칠 수 있는 함정들과 해결 방법을 함께 다루겠습니다.

1. CommonJS 모듈

기본 사용법

내보내기 (module.exports): 다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// math.js
function add(a, b) {
    return a + b;
}
function subtract(a, b) {
    return a - b;
}
const PI = 3.14159;
// 방법 1: 객체로 내보내기
module.exports = {
    add,
    subtract,
    PI
};

가져오기 (require): 아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// app.js
const math = require('./math');
console.log(math.add(10, 5));       // 15
console.log(math.subtract(10, 5));  // 5
console.log(math.PI);               // 3.14159

exports vs module.exports

exportsmodule.exports의 차이를 정확히 이해하는 것이 중요합니다:

// ✅ exports 사용 (속성 추가)
// exports는 module.exports를 가리키는 참조 변수
// 속성을 추가하는 방식으로만 사용 가능
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;
// 결과: { add: [Function], subtract: [Function] }
// ✅ module.exports 사용 (전체 교체)
// module.exports는 실제로 반환되는 객체
// 완전히 새로운 객체로 교체 가능
module.exports = {
    add: (a, b) => a + b,
    subtract: (a, b) => a - b
};
// 결과: { add: [Function], subtract: [Function] }
// ❌ 잘못된 사용
exports = {
    add: (a, b) => a + b  // 작동 안 함!
};
// exports 변수에 새 객체를 할당하면
// module.exports와의 참조가 끊어짐
// require()는 module.exports를 반환하므로 이 변경사항은 무시됨

올바른 이해: Node.js가 모듈을 로드할 때 내부적으로 어떻게 동작하는지 이해하면 혼란을 피할 수 있습니다: 다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// Node.js 내부 동작 (개념적 설명)
function require(modulePath) {
    // 1. 빈 module 객체 생성
    const module = { exports: {} };
    
    // 2. exports는 module.exports를 참조
    // 이것이 핵심! exports는 단순히 참조 변수
    const exports = module.exports;
    
    // 3. 모듈 코드를 함수로 감싸서 실행
    (function(module, exports) {
        // 여기서 실제 모듈 코드가 실행됨
        
        exports.add = ...;        // ✅ OK - module.exports에 속성 추가
        module.exports = ...;     // ✅ OK - module.exports 자체를 교체
        exports = ...;            // ❌ 안됨 - exports 참조만 끊김, module.exports는 그대로
    })(module, exports);
    
    // 4. 최종적으로 module.exports를 반환
    // exports가 아닌 module.exports를 반환!
    return module.exports;
}

핵심 규칙:

  • exports.xxx = ... → 속성 추가 (OK)
  • module.exports = ... → 전체 교체 (OK)
  • exports = ... → 참조만 끊김 (작동 안 함)

다양한 내보내기 패턴

패턴 1: 여러 함수 내보내기 아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// utils.js
exports.formatDate = (date) => {
    return date.toISOString().split('T')[0];
};
exports.capitalize = (str) => {
    return str.charAt(0).toUpperCase() + str.slice(1);
};
exports.randomInt = (min, max) => {
    return Math.floor(Math.random() * (max - min + 1)) + min;
};

패턴 2: 클래스 내보내기 아래 코드는 javascript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// user.js
// 타입 정의
class User {
    constructor(name, email) {
        this.name = name;
        this.email = email;
    }
    
    greet() {
        return `안녕하세요, ${this.name}님!`;
    }
}
module.exports = User;

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// app.js
const User = require('./user');
const user = new User('홍길동', 'hong@example.com');
console.log(user.greet());

패턴 3: 싱글톤 패턴 다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// database.js
class Database {
    constructor() {
        this.connection = null;
    }
    
    connect() {
        if (!this.connection) {
            this.connection = { connected: true };
            console.log('데이터베이스 연결됨');
        }
        return this.connection;
    }
}
// 싱글톤 인스턴스 내보내기
module.exports = new Database();

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// app.js
const db = require('./database');
db.connect();  // 첫 연결
// 다른 파일에서도 같은 인스턴스
const db2 = require('./database');
db2.connect();  // 이미 연결됨 (같은 인스턴스)

패턴 4: 팩토리 함수 아래 코드는 javascript를 사용한 구현 예제입니다. 함수를 통해 로직을 구현합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// logger.js
// 함수 정의 및 구현
function createLogger(prefix) {
    return {
        log: (message) => {
            console.log(`[${prefix}] ${message}`);
        },
        error: (message) => {
            console.error(`[${prefix}] ERROR: ${message}`);
        }
    };
}
module.exports = createLogger;

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// app.js
const createLogger = require('./logger');
const appLogger = createLogger('APP');
const dbLogger = createLogger('DB');
appLogger.log('서버 시작');
dbLogger.log('데이터베이스 연결');

2. ES Modules

설정

방법 1: package.json

{
  "type": "module"
}

방법 2: .mjs 확장자 다음은 간단한 javascript 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// math.mjs
export function add(a, b) {
    return a + b;
}

Named Export

다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// math.mjs
export function add(a, b) {
    return a + b;
}
export function subtract(a, b) {
    return a - b;
}
export const PI = 3.14159;
// 또는 한 번에
function multiply(a, b) {
    return a * b;
}
function divide(a, b) {
    return a / b;
}
export { multiply, divide };

가져오기: 아래 코드는 javascript를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// app.mjs
import { add, subtract, PI } from './math.mjs';
console.log(add(10, 5));  // 15
// 이름 변경
import { add as plus } from './math.mjs';
console.log(plus(10, 5));  // 15
// 모두 가져오기
import * as math from './math.mjs';
console.log(math.add(10, 5));

Default Export

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// calculator.mjs
export default class Calculator {
    add(a, b) {
        return a + b;
    }
    
    subtract(a, b) {
        return a - b;
    }
}
// Named + Default
export const VERSION = '1.0.0';

가져오기: 아래 코드는 javascript를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

// app.mjs
import Calculator, { VERSION } from './calculator.mjs';
const calc = new Calculator();
console.log(calc.add(10, 5));  // 15
console.log(VERSION);           // 1.0.0

동적 import

아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 조건부 로딩
async function loadModule() {
    if (condition) {
        const module = await import('./heavy-module.mjs');
        module.doSomething();
    }
}
// 지연 로딩
button.addEventListener('click', async () => {
    const { processData } = await import('./data-processor.mjs');
    processData();
});

3. CommonJS vs ES Modules

비교표

특징CommonJSES Modules
문법require, module.exportsimport, export
로딩동기 (런타임)비동기 (정적 분석)
파일 확장자.js.mjs 또는 .js (with "type": "module")
기본 내보내기module.exports = ...export default ...
Named 내보내기exports.name = ...export const name = ...
동적 로딩✅ (기본)✅ (import())
트리 쉐이킹
브라우저 지원
Node.js 지원✅ (기본)✅ (v12+)

언제 무엇을 사용할까?

CommonJS 사용:

  • ✅ 기존 Node.js 프로젝트
  • ✅ npm 패키지 대부분이 CommonJS
  • ✅ 동적 로딩이 많이 필요한 경우
  • ✅ 빠른 프로토타이핑 ES Modules 사용:
  • ✅ 새 프로젝트
  • ✅ 브라우저와 코드 공유
  • ✅ 트리 쉐이킹 필요 (번들 크기 최적화)
  • ✅ 정적 분석 도구 활용

혼용 (상호 운용성)

CommonJS에서 ES Modules 사용: 아래 코드는 javascript를 사용한 구현 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// CommonJS 파일
async function loadESModule() {
    const module = await import('./es-module.mjs');
    module.default();
}

ES Modules에서 CommonJS 사용: 아래 코드는 javascript를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

// ES Modules 파일
import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const commonjsModule = require('./commonjs-module.js');

4. 내장 모듈

주요 내장 모듈

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 파일 시스템
const fs = require('fs');
const fsPromises = require('fs').promises;
// 경로 처리
const path = require('path');
// HTTP/HTTPS
const http = require('http');
const https = require('https');
// URL 처리
const url = require('url');
// 쿼리스트링
const querystring = require('querystring');
// 운영체제 정보
const os = require('os');
// 암호화
const crypto = require('crypto');
// 이벤트
const EventEmitter = require('events');
// 스트림
const stream = require('stream');
// 자식 프로세스
const child_process = require('child_process');

fs (파일 시스템)

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const fs = require('fs').promises;
const path = require('path');
async function fileOperations() {
    try {
        // 파일 읽기
        const data = await fs.readFile('input.txt', 'utf8');
        console.log('파일 내용:', data);
        
        // 파일 쓰기
        await fs.writeFile('output.txt', 'Hello, Node.js!', 'utf8');
        
        // 파일 추가
        await fs.appendFile('output.txt', '\n추가 내용', 'utf8');
        
        // 파일 복사
        await fs.copyFile('output.txt', 'backup.txt');
        
        // 파일 이름 변경
        await fs.rename('backup.txt', 'backup-new.txt');
        
        // 파일 삭제
        await fs.unlink('backup-new.txt');
        
        // 파일 정보
        const stats = await fs.stat('output.txt');
        console.log('파일 크기:', stats.size);
        console.log('생성 시간:', stats.birthtime);
        console.log('수정 시간:', stats.mtime);
        
        // 디렉토리 생성
        await fs.mkdir('new-folder', { recursive: true });
        
        // 디렉토리 읽기
        const files = await fs.readdir('.');
        console.log('파일 목록:', files);
        
        // 디렉토리 삭제
        await fs.rmdir('new-folder');
        
    } catch (err) {
        console.error('에러:', err.message);
    }
}
fileOperations();

path (경로 처리)

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const path = require('path');
// 경로 결합
const filePath = path.join(__dirname, 'data', 'users.json');
console.log(filePath);
// C:\Users\JB\workspace\pkglog.com\data\users.json
// 절대 경로 생성
const absolutePath = path.resolve('data', 'users.json');
console.log(absolutePath);
// 파일 이름
console.log(path.basename('/foo/bar/file.txt'));  // file.txt
console.log(path.basename('/foo/bar/file.txt', '.txt'));  // file
// 디렉토리 이름
console.log(path.dirname('/foo/bar/file.txt'));  // /foo/bar
// 확장자
console.log(path.extname('file.txt'));  // .txt
// 경로 파싱
const parsed = path.parse('/foo/bar/file.txt');
console.log(parsed);
// {
//   root: '/',
//   dir: '/foo/bar',
//   base: 'file.txt',
//   ext: '.txt',
//   name: 'file'
// }
// 경로 정규화
console.log(path.normalize('/foo/bar/../baz'));  // /foo/baz
// 상대 경로
console.log(path.relative('/foo/bar', '/foo/baz/file.txt'));
// ../baz/file.txt

os (운영체제 정보)

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const os = require('os');
// 플랫폼
console.log('플랫폼:', os.platform());  // win32, darwin, linux
// CPU 정보
console.log('CPU:', os.cpus().length, '코어');
// 메모리
console.log('총 메모리:', (os.totalmem() / 1024 / 1024 / 1024).toFixed(2), 'GB');
console.log('여유 메모리:', (os.freemem() / 1024 / 1024 / 1024).toFixed(2), 'GB');
// 홈 디렉토리
console.log('홈:', os.homedir());
// 임시 디렉토리
console.log('임시:', os.tmpdir());
// 네트워크 인터페이스
console.log('네트워크:', os.networkInterfaces());

crypto (암호화)

다음은 javascript를 활용한 상세한 구현 코드입니다. 함수를 통해 로직을 구현합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

const crypto = require('crypto');
// 해시 생성
function hashPassword(password) {
    return crypto
        .createHash('sha256')
        .update(password)
        .digest('hex');
}
console.log(hashPassword('mypassword'));
// 89e01536ac207279409d4de1e5253e01f4a1769e696db0d6062ca9b8f56767c8
// 랜덤 문자열
const randomString = crypto.randomBytes(16).toString('hex');
console.log(randomString);
// a3f5c8b2e9d1f4a7c6b8e2d9f1a4c7b6
// UUID
const { randomUUID } = require('crypto');
console.log(randomUUID());
// 550e8400-e29b-41d4-a716-446655440000

5. 모듈 캐싱

캐싱 동작

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// counter.js
let count = 0;
exports.increment = () => {
    count++;
    console.log('Count:', count);
};
exports.getCount = () => count;

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// app.js
const counter1 = require('./counter');
const counter2 = require('./counter');
counter1.increment();  // Count: 1
counter2.increment();  // Count: 2
console.log(counter1.getCount());  // 2
console.log(counter2.getCount());  // 2
// counter1과 counter2는 같은 인스턴스!
console.log(counter1 === counter2);  // true

설명:

  • Node.js는 모듈을 처음 로드할 때 캐싱
  • 같은 모듈을 여러 번 require해도 한 번만 실행
  • 모든 require는 같은 인스턴스 반환

캐시 확인 및 삭제

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 캐시된 모듈 확인
console.log(require.cache);
// 캐시 삭제 (테스트 용도)
delete require.cache[require.resolve('./counter')];
// 다시 로드하면 새 인스턴스
const counter3 = require('./counter');
counter3.increment();  // Count: 1 (새로 시작)

6. 순환 참조 (Circular Dependency)

문제 상황

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// a.js
const b = require('./b');
exports.name = 'Module A';
exports.greet = () => {
    console.log(`A: ${b.name}`);
};

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// b.js
// 변수 선언 및 초기화
const a = require('./a');
exports.name = 'Module B';
exports.greet = () => {
    console.log(`B: ${a.name}`);
};

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// app.js
// 변수 선언 및 초기화
const a = require('./a');
const b = require('./b');
a.greet();  // A: Module B
b.greet();  // B: undefined (순환 참조!)

문제: b.jsa.js를 로드할 때, a.js는 아직 완전히 로드되지 않음.

해결 방법

방법 1: 구조 재설계 (권장)

// shared.js
exports.nameA = 'Module A';
exports.nameB = 'Module B';

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// a.js
const shared = require('./shared');
exports.greet = () => {
    console.log(`A: ${shared.nameB}`);
};

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// b.js
// 변수 선언 및 초기화
const shared = require('./shared');
exports.greet = () => {
    console.log(`B: ${shared.nameA}`);
};

방법 2: 지연 로딩 (Lazy Loading) 아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// b.js
exports.name = 'Module B';
exports.greet = () => {
    // 함수 실행 시점에 로드
    const a = require('./a');
    console.log(`B: ${a.name}`);
};

방법 3: 의존성 주입 아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// a.js
exports.name = 'Module A';
exports.setB = (b) => {
    exports.b = b;
};
exports.greet = () => {
    console.log(`A: ${exports.b.name}`);
};

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// app.js
const a = require('./a');
const b = require('./b');
a.setB(b);
b.setA(a);
a.greet();  // A: Module B
b.greet();  // B: Module A

7. 모듈 해석 (Module Resolution)

모듈 경로 규칙

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 1. 상대 경로
require('./math');        // 같은 폴더
require('../utils/math'); // 상위 폴더
require('./lib/math');    // 하위 폴더
// 2. 절대 경로
require('/home/user/project/math');
// 3. 패키지 이름 (node_modules)
require('express');
require('lodash');
// 4. 내장 모듈
require('fs');
require('http');

파일 확장자 생략

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// 다음 순서로 검색
require('./math');
// 1. ./math.js
// 2. ./math.json
// 3. ./math.node (네이티브 모듈)
// 4. ./math/index.js
// 5. ./math/package.json의 "main" 필드

node_modules 검색

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// require('express') 실행 시 검색 순서
// 1. ./node_modules/express
// 2. ../node_modules/express
// 3. ../../node_modules/express
// ....(루트까지 계속)

확인: 아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

console.log(require.resolve('express'));
// /home/user/project/node_modules/express/index.js
console.log(require.resolve.paths('express'));
// [ '/home/user/project/node_modules',
//   '/home/user/node_modules',
//   '/home/node_modules',
//   '/node_modules' ]

8. 실전 예제

예제 1: 설정 모듈

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// config.js
require('dotenv').config();
const config = {
    server: {
        port: process.env.PORT || 3000,
        host: process.env.HOST || 'localhost'
    },
    database: {
        host: process.env.DB_HOST || 'localhost',
        port: process.env.DB_PORT || 5432,
        name: process.env.DB_NAME || 'mydb',
        user: process.env.DB_USER || 'postgres',
        password: process.env.DB_PASSWORD || '
    },
    jwt: {
        secret: process.env.JWT_SECRET || 'default-secret',
        expiresIn: '1h'
    },
    isDevelopment: process.env.NODE_ENV === 'development',
    isProduction: process.env.NODE_ENV === 'production'
};
module.exports = config;

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// server.js
const config = require('./config');
console.log(`서버 포트: ${config.server.port}`);
console.log(`환경: ${config.isDevelopment ? '개발' : '운영'}`);

예제 2: 로거 모듈

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// logger.js
const fs = require('fs');
const path = require('path');
class Logger {
    constructor(logFile) {
        this.logFile = logFile;
    }
    
    _write(level, message) {
        const timestamp = new Date().toISOString();
        const logMessage = `[${timestamp}] [${level}] ${message}\n`;
        
        // 콘솔 출력
        console.log(logMessage.trim());
        
        // 파일 저장
        fs.appendFileSync(this.logFile, logMessage, 'utf8');
    }
    
    info(message) {
        this._write('INFO', message);
    }
    
    error(message) {
        this._write('ERROR', message);
    }
    
    warn(message) {
        this._write('WARN', message);
    }
    
    debug(message) {
        if (process.env.NODE_ENV === 'development') {
            this._write('DEBUG', message);
        }
    }
}
// 싱글톤 인스턴스
const logger = new Logger(path.join(__dirname, 'app.log'));
module.exports = logger;

아래 코드는 javascript를 사용한 구현 예제입니다. 에러 처리를 통해 안정성을 확보합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// app.js
const logger = require('./logger');
logger.info('서버 시작');
logger.error('데이터베이스 연결 실패');
logger.warn('메모리 사용량 높음');
logger.debug('디버그 정보');

예제 3: 데이터베이스 모듈

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// database.js
class Database {
    constructor() {
        this.connection = null;
        this.connected = false;
    }
    
    async connect(config) {
        if (this.connected) {
            console.log('이미 연결됨');
            return this.connection;
        }
        
        try {
            // 실제로는 데이터베이스 연결 로직
            this.connection = {
                host: config.host,
                port: config.port,
                database: config.database
            };
            this.connected = true;
            
            console.log(`데이터베이스 연결 성공: ${config.host}:${config.port}`);
            return this.connection;
        } catch (err) {
            console.error('데이터베이스 연결 실패:', err.message);
            throw err;
        }
    }
    
    async query(sql, params = []) {
        if (!this.connected) {
            throw new Error('데이터베이스에 연결되지 않음');
        }
        
        console.log('쿼리 실행:', sql, params);
        // 실제 쿼리 실행 로직
        return [];
    }
    
    async close() {
        if (this.connected) {
            this.connection = null;
            this.connected = false;
            console.log('데이터베이스 연결 종료');
        }
    }
}
// 싱글톤
module.exports = new Database();

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// app.js
const db = require('./database');
const config = require('./config');
async function main() {
    try {
        await db.connect(config.database);
        
        const users = await db.query('SELECT * FROM users WHERE age > ?', [18]);
        console.log('사용자:', users);
        
        await db.close();
    } catch (err) {
        console.error('에러:', err.message);
    }
}
main();

예제 4: API 클라이언트 모듈

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// api-client.js
const https = require('https');
class ApiClient {
    constructor(baseUrl) {
        this.baseUrl = baseUrl;
    }
    
    request(path, options = {}) {
        return new Promise((resolve, reject) => {
            const url = new URL(path, this.baseUrl);
            
            const req = https.request(url, {
                method: options.method || 'GET',
                headers: {
                    'Content-Type': 'application/json',
                    ...options.headers
                }
            }, (res) => {
                let data = ';
                
                res.on('data', (chunk) => {
                    data += chunk;
                });
                
                res.on('end', () => {
                    try {
                        const json = JSON.parse(data);
                        resolve(json);
                    } catch (err) {
                        reject(err);
                    }
                });
            });
            
            req.on('error', reject);
            
            if (options.body) {
                req.write(JSON.stringify(options.body));
            }
            
            req.end();
        });
    }
    
    get(path) {
        return this.request(path, { method: 'GET' });
    }
    
    post(path, body) {
        return this.request(path, { method: 'POST', body });
    }
}
module.exports = ApiClient;

다음은 javascript를 활용한 상세한 구현 코드입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// app.js
const ApiClient = require('./api-client');
const client = new ApiClient('https://api.github.com');
async function fetchUser(username) {
    try {
        const user = await client.get(`/users/${username}`);
        console.log('사용자:', user.name);
        console.log('저장소:', user.public_repos);
    } catch (err) {
        console.error('에러:', err.message);
    }
}
fetchUser('torvalds');

9. package.json 심화

필수 필드

아래 코드는 json를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

{
  "name": "my-package",
  "version": "1.0.0",
  "description": "패키지 설명",
  "main": "index.js",
  "scripts": {
    "start": "node index.js",
    "test": "jest"
  },
  "keywords": ["node", "javascript"],
  "author": "Your Name <your@email.com>",
  "license": "MIT"
}

dependencies vs devDependencies

아래 코드는 json를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

{
  "dependencies": {
    "express": "^4.18.2",
    "mongoose": "^7.0.0"
  },
  "devDependencies": {
    "nodemon": "^3.0.1",
    "jest": "^29.5.0",
    "eslint": "^8.50.0"
  }
}

차이:

  • dependencies: 프로덕션에서 필요한 패키지
  • devDependencies: 개발 중에만 필요한 패키지
# 프로덕션 설치 (devDependencies 제외)
npm install --production

버전 관리 (Semantic Versioning)

아래 코드는 json를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

{
  "dependencies": {
    "express": "^4.18.2"
  }
}

버전 형식: MAJOR.MINOR.PATCH

  • 4.18.2: 정확히 4.18.2만
  • ^4.18.2: 4.18.2 이상, 5.0.0 미만 (MINOR, PATCH 업데이트 허용)
  • ~4.18.2: 4.18.2 이상, 4.19.0 미만 (PATCH만 업데이트 허용)
  • *: 최신 버전
  • >=4.18.2: 4.18.2 이상

scripts 활용

다음은 json를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

{
  "scripts": {
    "start": "node server.js",
    "dev": "nodemon server.js",
    "test": "jest",
    "test:watch": "jest --watch",
    "lint": "eslint .",
    "lint:fix": "eslint . --fix",
    "build": "webpack --mode production",
    "clean": "rm -rf dist",
    "prebuild": "npm run clean",
    "postbuild": "echo 'Build complete!'",
    "deploy": "npm run build && npm run upload"
  }
}

실행 순서: 다음은 간단한 bash 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

npm run build
# 1. prebuild 실행
# 2. build 실행
# 3. postbuild 실행

10. 모듈 패턴

싱글톤 패턴

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// database.js
class Database {
    constructor() {
        if (Database.instance) {
            return Database.instance;
        }
        
        this.connection = null;
        Database.instance = this;
    }
    
    connect() {
        if (!this.connection) {
            this.connection = { connected: true };
            console.log('연결됨');
        }
    }
}
module.exports = new Database();

팩토리 패턴

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 함수를 통해 로직을 구현합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// user-factory.js
class User {
    constructor(name, role) {
        this.name = name;
        this.role = role;
    }
}
class Admin extends User {
    constructor(name) {
        super(name, 'admin');
        this.permissions = ['read', 'write', 'delete'];
    }
}
class Guest extends User {
    constructor(name) {
        super(name, 'guest');
        this.permissions = ['read'];
    }
}
function createUser(name, type) {
    switch (type) {
        case 'admin':
            return new Admin(name);
        case 'guest':
            return new Guest(name);
        default:
            return new User(name, 'user');
    }
}
module.exports = { createUser };

모듈 패턴 (Private 변수)

다음은 javascript를 활용한 상세한 구현 코드입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// counter.js
const counter = (() => {
    // Private 변수
    let count = 0;
    
    // Public API
    return {
        increment() {
            count++;
            return count;
        },
        decrement() {
            count--;
            return count;
        },
        getCount() {
            return count;
        },
        reset() {
            count = 0;
        }
    };
})();
module.exports = counter;

11. 자주 발생하는 문제

문제 1: Cannot find module

에러:

Error: Cannot find module './math'

원인:

  • 파일 경로 오타
  • 확장자 누락 (.mjs는 명시 필요)
  • 패키지 미설치 해결: 아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// ✅ 상대 경로 확인
require('./math');  // math.js가 같은 폴더에 있어야 함
// ✅ 절대 경로 사용
const path = require('path');
require(path.join(__dirname, 'math'));
// ✅ 패키지 설치
npm install express

문제 2: ES Modules 오류

에러:

SyntaxError: Cannot use import statement outside a module

해결: 다음은 간단한 json 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// package.json
{
  "type": "module"
}

또는 .mjs 확장자 사용.

문제 3: __dirname, __filename 없음 (ES Modules)

문제:

// ES Modules에서는 __dirname, __filename 없음
console.log(__dirname);  // ReferenceError

해결: 아래 코드는 javascript를 사용한 구현 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
console.log(__dirname);
console.log(__filename);

문제 4: 순환 참조

증상: 모듈이 undefined 또는 일부만 로드됨 해결: 위 “순환 참조” 섹션 참조

12. 실전 팁

모듈 구조화

다음은 code를 활용한 상세한 구현 코드입니다. 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

src/
├── config/
│   ├── database.js
│   ├── server.js
│   └── index.js
├── models/
│   ├── user.js
│   └── post.js
├── controllers/
│   ├── userController.js
│   └── postController.js
├── routes/
│   ├── userRoutes.js
│   └── postRoutes.js
├── middlewares/
│   ├── auth.js
│   └── errorHandler.js
├── utils/
│   ├── logger.js
│   └── validator.js
└── index.js

index.js 패턴

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// models/index.js
const User = require('./user');
const Post = require('./post');
const Comment = require('./comment');
module.exports = {
    User,
    Post,
    Comment
};

아래 코드는 javascript를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

// app.js
const { User, Post } = require('./models');
const user = new User('홍길동');
const post = new Post('제목');

환경별 설정

아래 코드는 javascript를 사용한 구현 예제입니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// config/index.js
const development = require('./development');
const production = require('./production');
const test = require('./test');
const configs = {
    development,
    production,
    test
};
const env = process.env.NODE_ENV || 'development';
module.exports = configs[env];

에러 처리

다음은 javascript를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// utils/errors.js
// 타입 정의
class AppError extends Error {
    constructor(message, statusCode) {
        super(message);
        this.statusCode = statusCode;
        this.isOperational = true;
        
        Error.captureStackTrace(this, this.constructor);
    }
}
class NotFoundError extends AppError {
    constructor(message = '리소스를 찾을 수 없습니다') {
        super(message, 404);
    }
}
class ValidationError extends AppError {
    constructor(message = '유효하지 않은 입력입니다') {
        super(message, 400);
    }
}
module.exports = {
    AppError,
    NotFoundError,
    ValidationError
};

정리

핵심 요약

  1. CommonJS: require, module.exports (Node.js 기본)
  2. ES Modules: import, export (표준, 최신)
  3. 내장 모듈: fs, path, http, os, crypto
  4. 모듈 캐싱: 한 번 로드하면 캐시됨
  5. 순환 참조: 구조 재설계 또는 지연 로딩으로 해결
  6. package.json: 프로젝트 메타데이터, 의존성 관리

비교: CommonJS vs ES Modules

특징CommonJSES Modules
문법require/module.exportsimport/export
로딩동기, 런타임비동기, 정적
동적 로딩기본 지원import() 사용
트리 쉐이킹불가가능
브라우저불가가능

다음 단계

추천 학습 자료

공식 문서:


관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3