[2026] Node.js Module System: CommonJS and ES Modules Explained

[2026] Node.js Module System: CommonJS and ES Modules Explained

이 글의 핵심

Master Node.js modules: require vs import, module.exports vs export, resolution, caching, circular dependencies, package.json, and interoperability—essential for any Node.js tutorial or backend project.

Introduction

What is a module?

A module is a reusable unit of code in its own file. Node.js supports two systems:

  1. CommonJS — default in Node (require, module.exports)
  2. ES modules — standard ECMAScript syntax (import, export) Benefits:
  • Reuse code across files
  • Avoid polluting the global scope
  • Keep features separated for maintenance
  • Express dependencies explicitly
  • Test units in isolation

1. CommonJS modules

Basics

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

// math.js
function add(a, b) {
    return a + b;
}
function subtract(a, b) {
    return a - b;
}
const PI = 3.14159;
module.exports = {
    add,
    subtract,
    PI
};

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

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

exports vs module.exports

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

// OK: add properties to exports (same object as module.exports)
exports.add = (a, b) => a + b;
exports.subtract = (a, b) => a - b;
// OK: replace module.exports entirely
module.exports = {
    add: (a, b) => a + b,
    subtract: (a, b) => a - b
};
// WRONG: reassigning exports breaks the link to module.exports
exports = {
    add: (a, b) => a + b
};

Conceptually, Node wraps your file and passes module and exports where exports starts as a reference to module.exports. Reassigning exports only changes the local variable. Rules:

  • exports.foo = ... — OK
  • module.exports = ... — OK
  • exports = { ....} — does not change what require returns

Export patterns

Multiple functions:

// utils.js
exports.formatDate = (date) => date.toISOString().split('T')[0];
exports.capitalize = (str) => str.charAt(0).toUpperCase() + str.slice(1);

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

// user.js
class User {
    constructor(name, email) {
        this.name = name;
        this.email = email;
    }
    greet() {
        return `Hello, ${this.name}!`;
    }
}
module.exports = User;

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

// database.js
class Database {
    constructor() {
        this.connection = null;
    }
    connect() {
        if (!this.connection) {
            this.connection = { connected: true };
        }
        return this.connection;
    }
}
module.exports = new Database();

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

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

2. ES modules

Setup

package.json:

{
  "type": "module"
}

Or use the .mjs extension.

Named exports

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

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

다음은 간단한 javascript 코드 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

// app.mjs
import { add, subtract, PI } from './math.mjs';
import { add as plus } from './math.mjs';
import * as math from './math.mjs';

Default export

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

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

Dynamic import()

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

async function loadModule() {
    if (condition) {
        const mod = await import('./heavy-module.mjs');
        mod.doSomething();
    }
}

3. CommonJS vs ES modules

FeatureCommonJSES modules
Syntaxrequire, module.exportsimport, export
LoadingSynchronous require at runtimeParsed statically; async load
Extension.js (default).mjs or "type": "module"
Default exportmodule.exports = ...export default
Tree shakingLimitedYes
BrowserNo (without bundler)Native in modern browsers
Use CommonJS for legacy code, many older packages, or quick scripts.
Use ES modules for new apps, shared browser/Node code, and bundler-friendly tree shaking.

Interop

From CommonJS, load ESM: 다음은 간단한 javascript 코드 예제입니다. 비동기 처리를 통해 효율적으로 작업을 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

async function loadESM() {
    const mod = await import('./es-module.mjs');
    mod.default();
}

From ESM, load CommonJS:

import { createRequire } from 'module';
const require = createRequire(import.meta.url);
const cjs = require('./commonjs-module.js');

4. Built-in modules (overview)

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

const fs = require('fs');
const path = require('path');
const http = require('http');
const https = require('https');
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 (promises)

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

const fs = require('fs').promises;
const path = require('path');
async function fileOperations() {
    try {
        const data = await fs.readFile('input.txt', 'utf8');
        await fs.writeFile('output.txt', 'Hello, Node.js!', 'utf8');
        await fs.appendFile('output.txt', '\nMore', '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, stats.mtime);
        await fs.mkdir('new-folder', { recursive: true });
        const files = await fs.readdir('.');
        await fs.rmdir('new-folder');
    } catch (err) {
        console.error('Error:', err.message);
    }
}
fileOperations();

path

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

// 변수 선언 및 초기화
const path = require('path');
const filePath = path.join(__dirname, 'data', 'users.json');
const absolutePath = path.resolve('data', 'users.json');
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
console.log(path.parse('/foo/bar/file.txt'));
console.log(path.normalize('/foo/bar/../baz'));
console.log(path.relative('/foo/bar', '/foo/baz/file.txt'));

os

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

// 변수 선언 및 초기화
const os = require('os');
console.log('platform:', os.platform());
console.log('CPUs:', os.cpus().length);
console.log('total mem GB:', (os.totalmem() / 1024 / 1024 / 1024).toFixed(2));
console.log('free mem GB:', (os.freemem() / 1024 / 1024 / 1024).toFixed(2));
console.log('homedir:', os.homedir());
console.log('tmpdir:', os.tmpdir());
console.log('networkInterfaces:', os.networkInterfaces());

crypto

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

const crypto = require('crypto');
function hashPassword(password) {
    return crypto.createHash('sha256').update(password).digest('hex');
}
const randomString = crypto.randomBytes(16).toString('hex');
const { randomUUID } = require('crypto');
console.log(randomUUID());

5. Module caching

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

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

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

// 변수 선언 및 초기화
const counter1 = require('./counter');
const counter2 = require('./counter');
counter1.increment();
counter2.increment();
console.log(counter1 === counter2); // true

Clear cache (testing):

delete require.cache[require.resolve('./counter')];
const counter3 = require('./counter');

6. Circular dependencies

Problem: a.js requires b.js, b.js requires a.js — partially initialized exports may be undefined. Fix 1 — shared module:

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

Fix 2 — lazy require inside a function: 다음은 간단한 javascript 코드 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

exports.greet = () => {
    const a = require('./a');
    console.log(`B sees: ${a.name}`);
};

Fix 3 — dependency injection (pass dependencies after both load).

7. Module resolution

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

require('./math');           // relative
require('../utils/math');
require('express');          // node_modules
require('fs');               // built-in

Resolution order for ./math: math.js, math.json, math.node, math/index.js, or math/package.json main. require.resolve('express') shows the resolved path.

8. Practical examples

Common patterns include config (dotenv, structured module.exports), logger (singleton writing to disk), database wrapper singleton, and ApiClient with https.request. Translate user-facing strings for your locale in your own codebase.

9. package.json advanced

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

{
  "name": "my-package",
  "version": "1.0.0",
  "main": "index.js",
  "dependencies": { "express": "^4.18.2" },
  "devDependencies": { "nodemon": "^3.0.1" }
}

Semantic versioning: ^4.18.2 allows minor/patch updates below 5.0.0; ~4.18.2 allows patch only; pin exact versions when you need reproducible builds. Lifecycle npm scripts: prebuild, build, postbuild run in order when you npm run build.

10. Module patterns

Singleton with guard: 아래 코드는 javascript를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 코드를 직접 실행해보면서 동작을 확인해보세요.

class Database {
    constructor() {
        if (Database.instance) return Database.instance;
        this.connection = null;
        Database.instance = this;
    }
}
module.exports = new Database();

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

const counter = (() => {
    let count = 0;
    return {
        increment() { return ++count; },
        getCount() { return count; }
    };
})();
module.exports = counter;

11. Common problems

Cannot find module

Check path typos, install the package, or add "type": "module" / .mjs for ESM.

import outside a module

Add "type": "module" to package.json or rename to .mjs.

No __dirname in ESM

다음은 간단한 javascript 코드 예제입니다. 필요한 모듈을 import하고. 코드를 직접 실행해보면서 동작을 확인해보세요.

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

12. Project structure tip

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

src/
├── config/
├── models/
├── controllers/
├── routes/
├── middlewares/
├── utils/
└── index.js

Use models/index.js to re-export models for cleaner imports.

Summary

  1. CommonJSrequire / module.exports
  2. ES modulesimport / export
  3. Caching — one evaluation per resolved path
  4. Circular deps — refactor, lazy load, or inject
  5. package.json — metadata, semver, scripts

Next steps

Resources


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