Modules in Node.js
Modules are the blocks of encapsulated code that communicate with an external application on the basis of their related functionality. Modules can be a single file or a collection of multiple files/folders.
Node.js uses the CommonJS module system by default, though it also supports ES Modules (ESM) in newer versions.
The require Function
To include a module, use the require() function with the name of the module.
const fs = require('fs'); // Built-in module
Creating Custom Modules
Let’s create a simple module that exports some math functions.
math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
// Exporting functions
module.exports = {
add,
subtract
};
app.js
const math = require('./math');
console.log(math.add(5, 3)); // Output: 8
console.log(math.subtract(5, 3)); // Output: 2
Alternative Export Syntax
You can also attach properties directly to exports (which is a shorthand for module.exports).
exports.multiply = (a, b) => a * b;
exports.divide = (a, b) => a / b;
Module Wrapper Function
Under the hood, Node.js doesn’t execute your code directly. It wraps it inside a function wrapper:
(function(exports, require, module, __filename, __dirname) {
// Your module code actually lives in here
});
This is why variables defined in a module are scoped to that module (private) rather than being global.
Built-in Modules
Node.js comes with many useful built-in modules. We will explore these in depth in later chapters, but here are a few common ones:
fs: File system operations.
http: Create HTTP servers.
path: Utilities for working with file and directory paths.
os: Operating system information.
events: Event emitter.
Example: The os Module
const os = require('os');
console.log('Platform:', os.platform());
console.log('Architecture:', os.arch());
console.log('Free Memory:', os.freemem());
console.log('Total Memory:', os.totalmem());
console.log('Uptime:', os.uptime());
ES Modules (import/export)
Node.js has added support for ES Modules, which is the standard in modern JavaScript (and browsers).
To use ES Modules, you can either:
- Use the
.mjs extension for your files.
- Add
"type": "module" to your package.json.
math.mjs
export const add = (a, b) => a + b;
export default function log(msg) {
console.log(msg);
}
app.mjs
import log, { add } from './math.mjs';
log('Result: ' + add(2, 3));
CommonJS vs ES Modules Comparison
| Feature | CommonJS | ES Modules |
|---|
| Syntax | require() / module.exports | import / export |
| Loading | Synchronous | Asynchronous |
| File Extension | .js or .cjs | .mjs or .js with “type”: “module” |
| Top-level await | ❌ Not supported | ✅ Supported |
| Dynamic imports | require(variable) | import(variable) |
| Tree shaking | ❌ Limited | ✅ Supported |
| Browser support | ❌ No | ✅ Yes |
Module Caching
Node.js caches modules after the first require(). Subsequent calls return the cached version.
// counter.js
let count = 0;
module.exports = {
increment: () => ++count,
getCount: () => count
};
// app.js
const counter1 = require('./counter');
const counter2 = require('./counter');
counter1.increment();
counter1.increment();
console.log(counter2.getCount()); // Output: 2 (same instance!)
Module caching can lead to unexpected behavior if you expect fresh instances. For stateful modules, consider exporting a factory function instead.
Circular Dependencies
Node.js handles circular dependencies, but they can cause issues.
// a.js
console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done =', b.done);
exports.done = true;
console.log('a done');
// b.js
console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done =', a.done);
exports.done = true;
console.log('b done');
// main.js
const a = require('./a.js');
const b = require('./b.js');
// Output:
// a starting
// b starting
// in b, a.done = false <-- Incomplete!
// b done
// in a, b.done = true
// a done
Best Practice: Avoid circular dependencies by restructuring your code. Extract shared code into a separate module.
The path Module
The path module provides utilities for working with file and directory paths.
const path = require('path');
// Join paths (handles OS-specific separators)
const filePath = path.join(__dirname, 'data', 'users.json');
console.log(filePath); // /app/data/users.json (Unix) or \app\data\users.json (Windows)
// Resolve to absolute path
const absolute = path.resolve('data', 'users.json');
console.log(absolute); // Full absolute path
// Get file extension
console.log(path.extname('file.txt')); // .txt
// Get filename without extension
console.log(path.basename('file.txt', '.txt')); // file
// Get directory name
console.log(path.dirname('/users/john/file.txt')); // /users/john
// Parse path into components
const parsed = path.parse('/home/user/file.txt');
console.log(parsed);
// {
// root: '/',
// dir: '/home/user',
// base: 'file.txt',
// ext: '.txt',
// name: 'file'
// }
The url Module
const { URL } = require('url');
const myUrl = new URL('https://example.com:8080/path?name=John&age=30#section');
console.log(myUrl.href); // Full URL
console.log(myUrl.protocol); // https:
console.log(myUrl.host); // example.com:8080
console.log(myUrl.hostname); // example.com
console.log(myUrl.port); // 8080
console.log(myUrl.pathname); // /path
console.log(myUrl.search); // ?name=John&age=30
console.log(myUrl.hash); // #section
// URLSearchParams
console.log(myUrl.searchParams.get('name')); // John
myUrl.searchParams.append('country', 'USA');
console.log(myUrl.href);
Creating a Reusable Module
Let’s create a practical utility module:
// utils/logger.js
const colors = {
reset: '\x1b[0m',
red: '\x1b[31m',
green: '\x1b[32m',
yellow: '\x1b[33m',
blue: '\x1b[34m'
};
const formatDate = () => new Date().toISOString();
const logger = {
info: (msg) => console.log(`${colors.blue}[INFO]${colors.reset} ${formatDate()} - ${msg}`),
success: (msg) => console.log(`${colors.green}[SUCCESS]${colors.reset} ${formatDate()} - ${msg}`),
warn: (msg) => console.log(`${colors.yellow}[WARN]${colors.reset} ${formatDate()} - ${msg}`),
error: (msg) => console.log(`${colors.red}[ERROR]${colors.reset} ${formatDate()} - ${msg}`)
};
module.exports = logger;
// Usage:
// const logger = require('./utils/logger');
// logger.info('Server started');
// logger.error('Database connection failed');
Summary
- Node.js uses CommonJS (
require/module.exports) by default
- ES Modules (
import/export) are also supported
- Modules are cached after first load
- Avoid circular dependencies by restructuring code
- Use the
path module for cross-platform file paths
- Use the
url module for URL parsing and manipulation
- Create reusable modules to keep code DRY
**app.mjs**
```javascript
import log, { add } from './math.mjs';
log('Hello ES Modules');
console.log(add(2, 2));
Summary
- Modules allow you to organize code into reusable files.
- CommonJS (
require, module.exports) is the default system in Node.js.
- ES Modules (
import, export) are also supported and becoming more common.
- Node.js wraps every module in a wrapper function, providing local scope and variables like
__dirname.
- There are many built-in modules like
os, path, fs, etc.