Skip to main content
Node.js Module System

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:
  1. Use the .mjs extension for your files.
  2. 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

FeatureCommonJSES Modules
Syntaxrequire() / module.exportsimport / export
LoadingSynchronousAsynchronous
File Extension.js or .cjs.mjs or .js with “type”: “module”
Top-level await❌ Not supported✅ Supported
Dynamic importsrequire(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.