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:
- CommonJS — default in Node (
require,module.exports) - 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:
// 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:
// 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
// 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 = ...— OKmodule.exports = ...— OKexports = { ... }— does not change whatrequirereturns
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:
// user.js
class User {
constructor(name, email) {
this.name = name;
this.email = email;
}
greet() {
return `Hello, ${this.name}!`;
}
}
module.exports = User;
Singleton:
// database.js
class Database {
constructor() {
this.connection = null;
}
connect() {
if (!this.connection) {
this.connection = { connected: true };
}
return this.connection;
}
}
module.exports = new Database();
Factory:
// 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
// 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 };
// app.mjs
import { add, subtract, PI } from './math.mjs';
import { add as plus } from './math.mjs';
import * as math from './math.mjs';
Default export
// 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()
async function loadModule() {
if (condition) {
const mod = await import('./heavy-module.mjs');
mod.doSomething();
}
}
3. CommonJS vs ES modules
| Feature | CommonJS | ES modules |
|---|---|---|
| Syntax | require, module.exports | import, export |
| Loading | Synchronous require at runtime | Parsed statically; async load |
| Extension | .js (default) | .mjs or "type": "module" |
| Default export | module.exports = ... | export default |
| Tree shaking | Limited | Yes |
| Browser | No (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:
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)
The following example demonstrates the concept in 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)
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
// 변수 선언 및 초기화
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
// 변수 선언 및 초기화
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
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
The following example demonstrates the concept in javascript:
// counter.js
let count = 0;
exports.increment = () => {
count++;
console.log('Count:', count);
};
exports.getCount = () => count;
// 변수 선언 및 초기화
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:
exports.greet = () => {
const a = require('./a');
console.log(`B sees: ${a.name}`);
};
Fix 3 — dependency injection (pass dependencies after both load).
7. Module resolution
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
Configuration file:
{
"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:
class Database {
constructor() {
if (Database.instance) return Database.instance;
this.connection = null;
Database.instance = this;
}
}
module.exports = new Database();
IIFE module with private state:
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
import { fileURLToPath } from 'url';
import { dirname } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
12. Project structure tip
src/
├── config/
├── models/
├── controllers/
├── routes/
├── middlewares/
├── utils/
└── index.js
Use models/index.js to re-export models for cleaner imports.
Summary
- CommonJS —
require/module.exports - ES modules —
import/export - Caching — one evaluation per resolved path
- Circular deps — refactor, lazy load, or inject
- package.json — metadata, semver, scripts
Next steps
Resources
Related posts
- JavaScript modules (ESM vs CJS)
- Getting started with Node.js
- Async: callbacks, Promises, async/await
Frequently Asked Questions (FAQ)
Q. When would I use this in practice?
A. Master Node.js modules: require vs import, module.
Q. What should I read before this?
A. Follow the previous article or related articles links at the bottom of each post to learn in sequence.
Q. Where can I study this more deeply?
A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.
Related Articles (Internal Links)
Other articles related to this topic.
- Getting Started with Node.js: Install, Setup, and Hello
- Node.js Async Programming: Callbacks, Promises, and
- JavaScript 모듈 | ES6 Modules, CommonJS 완벽 정리
Keywords Covered in This Article (Related Search Terms)
This article covers Node.js, JavaScript, Modules, CommonJS, ES Modules, require, import, npm.