Node.js CommonJS Modules — Complete Guide to require, exports, and module Patterns
In this tutorial, you will learn about Node.js CommonJS Modules. We cover key concepts, practical examples, and best practices to help you master this topic.
Node.js CommonJS modules use require() and module.exports to organize code into reusable files, providing encapsulation and dependency management for server-side JavaScript.
What You'll Learn
By the end of this tutorial, you'll master CommonJS modules: require resolution, module.exports vs exports, caching behavior, circular dependencies, and organizing large codebases.
Why CommonJS Modules Matter
CommonJS is the default module system in Node.js. Every package on npm uses it. Understanding require resolution and caching prevents bugs and helps you structure maintainable applications like Doda Browser's sync server.
Real-World Use
A REST API has separate files for routes, controllers, models, and middleware. CommonJS modules let each file export specific functionality while keeping internal details private.
CommonJS Learning Path
flowchart LR
A[Global Objects] --> B[CommonJS Modules]
B --> C[ES Modules]
C --> D[Core Modules]
D --> E[File System]
A --> F{You Are Here}
style F fill:#f90,color:#fff
require() Basics
require() loads a module and returns its exports. Node.js resolves the path following a specific order.
// Load a core module
const fs = require("node:fs");
// Load a local module (relative path)
const config = require("./config");
// Load an npm package
const express = require("express");
Module Resolution Order
- Built-in core modules (node:fs, node:path)
- Relative or absolute path (./ or /)
- node_modules lookup (walks parent directories)
module.exports vs exports
Every module has a module object. module.exports is what require() returns. exports is a shorthand reference to module.exports.
// math.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;
module.exports = { add, subtract };
// app.js
const math = require("./math");
console.log(math.add(5, 3)); // 8
Warning: Reassigning exports
// WRONG — breaks the reference
exports = { add, subtract }; // module.exports is still {}
// RIGHT — attach to exports
exports.add = add;
exports.subtract = subtract;
Module Caching
Node.js caches modules after the first require. Subsequent requires return the same instance.
// counter.js
let count = 0;
module.exports = {
increment: () => ++count,
getCount: () => count
};
// a.js
const counter = require("./counter");
counter.increment();
// b.js
const counter = require("./counter");
console.log(counter.getCount()); // 1 — same instance
This caching is useful for shared state like database connections but can cause surprises with mutable objects.
Module Wrapper Function
Node.js wraps each module in a function before execution:
(function(exports, require, module, __filename, __dirname) {
// Your module code here
});
This explains why __dirname and __filename are available without importing them.
Loading JSON Files
const data = require("./config.json");
console.log(data.database.host);
Node.js automatically parses JSON and caches it like any module.
Circular Dependencies
Circular dependencies happen when module A requires module B, and B requires A. Node.js returns the partial exports at the time of the circular require.
// a.js
const b = require("./b");
console.log("A loaded");
module.exports = { name: "Module A" };
// b.js
const a = require("./a");
console.log(a); // {} — A's exports not ready yet
module.exports = { name: "Module B" };
Avoid circular dependencies. Restructure with a shared dependency or use Dependency Injection.
Common Mistakes
1. Reassigning exports Instead of module.exports
Reassigning exports breaks the reference to module.exports, and your module returns an empty object.
2. Forgetting ./ for Local Modules
require("math") looks in node_modules, not the local directory. Always use require("./math").
3. Relying on Module Caching for Fresh Data
If you need a new instance each time, export a Factory function instead of an object.
4. Creating Circular Dependencies
Circular dependencies cause partial exports and confusing bugs. Use a third module that both A and B import.
5. Loading Files Without Extensions
While Node.js tries .js, .json, and .node, always include the extension for clarity and performance.
Practice Questions
1. What does require("./math") return?
It returns the value of module.exports in the math.js file. If nothing is assigned, it returns an empty object.
2. Why does exports = { foo: "bar" } not work?
exports is a reference to module.exports. Reassigning exports breaks the reference. module.exports still points to the original empty object.
3. How does Node.js resolve require("lodash")?
Node.js looks in node_modules/lodash in the current directory, then parent directories, until found. If not found, it throws MODULE_NOT_FOUND.
4. What happens when you require the same module twice?
The second require returns the cached exports. The module code runs only once.
5. Challenge: Create a module that exports a Singleton database connection, ensuring the connection is created only on first require.
// db.js
let connection = null;
module.exports = {
getConnection: () => {
if (!connection) {
console.log("Creating new connection");
connection = { host: "localhost", port: 5432 };
}
return connection;
}
};
// app.js
const db1 = require("./db");
const db2 = require("./db");
console.log(db1.getConnection() === db2.getConnection()); // true
FAQ
Mini Project: Config Loader Module
Create a config module that loads settings from JSON with environment variable overrides.
// config.js
const fs = require("node:fs");
const path = require("node:path");
const defaults = JSON.parse(fs.readFileSync(path.join(__dirname, "defaults.json"), "utf8"));
const overrides = {};
for (const key of Object.keys(defaults)) {
overrides[key] = process.env[key.toUpperCase()] || defaults[key];
}
module.exports = overrides;
// defaults.json
{ "port": 3000, "host": "localhost", "debug": false }
// app.js
const config = require("./config");
console.log(config); // { port: 3000, host: "localhost", debug: false }
What's Next
Node.js ES Modules Node.js Core Modules Express.js
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro