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:
- 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.
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)
- 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.exportsis the actual object that gets returned byrequire()exportsis a shorthand reference tomodule.exports- If you reassign
exports = something, it breaks the reference — always usemodule.exportsfor 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
1// CommonJS Module System — Complete Guide23// === math.js — Exporting a module ===4// Method 1: Named exports using exports shorthand5exports.add = (a, b) => a + b;6exports.subtract = (a, b) => a - b;7exports.PI = 3.14159;89// Method 2: module.exports for a single export10// module.exports = class Calculator { ... };1112// Method 3: module.exports with an object13module.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};2324// === app.js — Importing modules ===25// 1. Import entire module26const math = require("./math");27console.log(math.add(2, 3)); // 52829// 2. Destructured import30const { add, subtract, PI } = require("./math");31console.log(add(10, 5)); // 1532console.log(PI); // 3.141593334// 3. Core modules (no path needed)35const fs = require("fs");36const path = require("path");37const os = require("os");3839// 4. npm packages (resolved from node_modules/)40// const express = require("express");41// const lodash = require("lodash");4243// 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}5354// 6. Module caching demonstration55// ❌ Common confusion: modules are cached!56// === counter.js ===57// let count = 0;58// module.exports = {59// increment: () => ++count,60// getCount: () => count,61// };6263// === 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!7071// 7. Check the module cache72console.log("\n--- Module Cache ---");73console.log(Object.keys(require.cache).slice(0, 5));7475// Clear a module from cache (forces reload)76// delete require.cache[require.resolve("./math")];7778// 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" };8384// === 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" };8889// 9. module.exports vs exports gotcha90// ❌ BAD — This doesn't work!91// exports = function() { return "broken"; };92// Because you're reassigning the reference, not the object9394// ✅ GOOD — This works95// module.exports = function() { return "works"; };9697// ✅ ALSO GOOD — Adding to existing exports98// exports.myFunc = function() { return "also works"; };
🏋️ Practice Exercise
Exercises:
- Create a
utilsmodule with 5+ utility functions and import them with destructuring in another file - Demonstrate module caching by creating a counter module — show that two
require()calls return the same instance - Create a circular dependency between two modules and observe the partial export behavior
- Build a simple plugin loader that dynamically
require()s modules from aplugins/directory - Write a module that exports a class using
module.exportsand use it to create instances in another file - Explore
require.cache— write a function that clears and reloads a specific module
⚠️ Common Mistakes
Assigning
exports = somethinginstead ofmodule.exports = something—exportsis just a reference; reassigning it breaks the link to the actual export objectNot understanding module caching —
require()returns the SAME object on subsequent calls, so mutations in one file affect all importersUsing relative paths without
./prefix —require('math')looks in node_modules, not the local directory; userequire('./math')for local filesCreating 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.