CommonJS Modules (require/module.exports)

📖 Concept

CommonJS (CJS) is Node.js's original module system and still the default in most Node.js projects. Every file in Node.js is treated as a separate module with its own scope.

How CommonJS works:

  1. Each file is wrapped in a module wrapper function:
(function(exports, require, module, __filename, __dirname) {
  // Your code here
});

This is why __filename, __dirname, require, module, and exports are available in every file — they're function parameters, not true globals.

  1. require() resolution algorithm:
require('X')
  1. If X is a core module (fs, http, path) → return core module
  2. If X starts with './' or '/' → load as file or directory
     a. Try X, X.js, X.json, X.node
     b. Try X/index.js, X/index.json, X/index.node
  3. Load from node_modules/ → walk up directory tree
     a. ./node_modules/X
     b. ../node_modules/X
     c. ../../node_modules/X (continues to root)
  1. Module caching: require() caches modules after the first load. Subsequent calls return the same object reference — this is both a feature (singletons) and a gotcha (shared state).

module.exports vs exports:

  • module.exports is the actual object that gets returned by require()
  • exports is a shorthand reference to module.exports
  • If you reassign exports = something, it breaks the reference — always use module.exports for non-object exports

When to use CommonJS:

  • Node.js scripts and CLIs
  • Existing projects that already use CJS
  • When you need require() dynamically (conditional imports)
  • When using older packages that only support CJS

🏠 Real-world analogy: CommonJS modules are like shipping containers — each one is sealed (scoped), has a manifest (exports), and gets loaded synchronously on the dock (require). Once unloaded, the container is cached in the warehouse.

💻 Code Example

codeTap to expand ⛶
1// CommonJS Module System — Complete Guide
2
3// === math.js — Exporting a module ===
4// Method 1: Named exports using exports shorthand
5exports.add = (a, b) => a + b;
6exports.subtract = (a, b) => a - b;
7exports.PI = 3.14159;
8
9// Method 2: module.exports for a single export
10// module.exports = class Calculator { ... };
11
12// Method 3: module.exports with an object
13module.exports = {
14 add: (a, b) => a + b,
15 subtract: (a, b) => a - b,
16 multiply: (a, b) => a * b,
17 divide: (a, b) => {
18 if (b === 0) throw new Error("Division by zero");
19 return a / b;
20 },
21 PI: 3.14159,
22};
23
24// === app.js — Importing modules ===
25// 1. Import entire module
26const math = require("./math");
27console.log(math.add(2, 3)); // 5
28
29// 2. Destructured import
30const { add, subtract, PI } = require("./math");
31console.log(add(10, 5)); // 15
32console.log(PI); // 3.14159
33
34// 3. Core modules (no path needed)
35const fs = require("fs");
36const path = require("path");
37const os = require("os");
38
39// 4. npm packages (resolved from node_modules/)
40// const express = require("express");
41// const lodash = require("lodash");
42
43// 5. Dynamic require (CJS-only feature)
44function loadPlugin(name) {
45 try {
46 const plugin = require(`./plugins/${name}`);
47 return plugin;
48 } catch (err) {
49 console.error(`Plugin ${name} not found`);
50 return null;
51 }
52}
53
54// 6. Module caching demonstration
55// ❌ Common confusion: modules are cached!
56// === counter.js ===
57// let count = 0;
58// module.exports = {
59// increment: () => ++count,
60// getCount: () => count,
61// };
62
63// === app.js ===
64// const counter1 = require("./counter");
65// const counter2 = require("./counter");
66// counter1.increment();
67// counter1.increment();
68// console.log(counter2.getCount()); // 2 ← Same instance!
69// console.log(counter1 === counter2); // true ← Same reference!
70
71// 7. Check the module cache
72console.log("\n--- Module Cache ---");
73console.log(Object.keys(require.cache).slice(0, 5));
74
75// Clear a module from cache (forces reload)
76// delete require.cache[require.resolve("./math")];
77
78// 8. Circular dependencies (CJS handles them gracefully)
79// === a.js ===
80// console.log("a: loading b...");
81// const b = require("./b");
82// module.exports = { fromA: "hello from A" };
83
84// === b.js ===
85// console.log("b: loading a...");
86// const a = require("./a"); // Gets PARTIAL export (whatever was set so far)
87// module.exports = { fromB: "hello from B" };
88
89// 9. module.exports vs exports gotcha
90// ❌ BAD — This doesn't work!
91// exports = function() { return "broken"; };
92// Because you're reassigning the reference, not the object
93
94// ✅ GOOD — This works
95// module.exports = function() { return "works"; };
96
97// ✅ ALSO GOOD — Adding to existing exports
98// exports.myFunc = function() { return "also works"; };

🏋️ Practice Exercise

Exercises:

  1. Create a utils module with 5+ utility functions and import them with destructuring in another file
  2. Demonstrate module caching by creating a counter module — show that two require() calls return the same instance
  3. Create a circular dependency between two modules and observe the partial export behavior
  4. Build a simple plugin loader that dynamically require()s modules from a plugins/ directory
  5. Write a module that exports a class using module.exports and use it to create instances in another file
  6. Explore require.cache — write a function that clears and reloads a specific module

⚠️ Common Mistakes

  • Assigning exports = something instead of module.exports = somethingexports is just a reference; reassigning it breaks the link to the actual export object

  • Not understanding module caching — require() returns the SAME object on subsequent calls, so mutations in one file affect all importers

  • Using relative paths without ./ prefix — require('math') looks in node_modules, not the local directory; use require('./math') for local files

  • Creating circular dependencies without understanding partial exports — module A importing B which imports A gets an incomplete A object

  • Putting side effects at the top level of modules (API calls, database connections) — they execute on first require(), which may happen at unexpected times

💼 Interview Questions

🎤 Mock Interview

Mock interview is powered by AI for CommonJS Modules (require/module.exports). Login to unlock this feature.