Chapter 2 of 17
~12 minModules & CommonJS
CommonJS is the module system Node.js has used from the start: every file is its own module, and `require`/`module.exports` control what gets shared between them. Understanding how resolution and caching work explains a lot of real-world module bugs.
require, module.exports and exports
Each file Node.js loads is wrapped in a function that receives its own `module`, `exports`, `require`, `__filename`, and `__dirname` - so variables declared in one file never leak into another. Whatever you assign to `module.exports` is exactly what a caller receives back from `require('./yourFile')`.
`exports` is simply a variable that starts out pointing at the same object as `module.exports`. Attaching properties to it (`exports.add = ...`) works because it mutates the shared object, but reassigning `exports` entirely (`exports = { add: ... }`) only rebinds the local variable - `module.exports` still points at the original object, so the reassignment is silently lost.
// math.js function add(a, b) { return a + b; } function subtract(a, b) { return a - b; } module.exports = { add, subtract }; // app.js const math = require('./math'); console.log(math.add(2, 3)); // 5 console.log(math.subtract(5, 2)); // 3
How require() Resolves a Path
When you call `require('./math')`, Node.js resolves it relative to the requiring file, tries `.js`, `.json`, and `.node` extensions in turn, and if the path is a directory, looks for a `package.json` `main` field or an `index.js`. A bare specifier with no leading `./` or `/`, like `require('lodash')`, instead walks up through `node_modules` folders starting from the current directory until it finds a match.
Modules Are Cached
The first time a module is required, Node.js executes it and stores the resulting `module.exports` in an internal cache keyed by the resolved file path. Every subsequent `require()` of that same file - from anywhere in the app - returns the cached object instead of re-running the file, which is why a module-level counter or a singleton database connection behaves the same across every file that requires it.
// counter.js let count = 0; module.exports = { increment() { count += 1; return count; }, }; // a.js const counterA = require('./counter'); console.log(counterA.increment()); // 1 // b.js (required after a.js has already run) const counterB = require('./counter'); console.log(counterB.increment()); // 2, not 1 - same cached instance
Circular Dependency Pitfalls
If module A requires module B and module B requires module A back, Node.js breaks the cycle by handing out whatever partial `module.exports` A had built up so far, rather than looping forever. If B's `require('./a')` happens before A has finished assigning its exports, B receives an incomplete (possibly empty) object.
The practical fix is usually to restructure code so the shared piece lives in its own third module that both A and B require, or to require the dependency lazily inside a function body (so it resolves after both modules have fully loaded) instead of at the top of the file.
// a.js console.log('a.js starting'); exports.done = false; const b = require('./b'); console.log('in a.js, b.message =', b.message); exports.done = true; // b.js console.log('b.js starting'); const a = require('./a'); console.log('in b.js, a.done =', a.done); // false - a.js has not finished yet exports.message = 'hello from b';
Coding challenge
Create a stringUtils.js module that exports two functions, capitalize(str) and reverse(str), using module.exports, and a second file that requires it and prints both functions applied to "hello".
// Write your solution here
Answer every question below to mark this chapter as complete.
Quiz Yourself
Score: 0/51. What happens if you do `exports = { foo: 1 }` instead of `module.exports = { foo: 1 }`?
2. When you call require() on the same file twice from two different modules, what do you get?
3. How does require() resolve `require('lodash')` versus `require('./utils')`?
4. What is the underlying cause of a circular-dependency bug between two CommonJS modules?
5. Which arguments does Node.js pass into the function each CommonJS file is wrapped in?