Skip to content

CommonJS Modules

CommonJS is the module system Node.js launched with, and it still powers a huge share of existing Node.js code. This lesson covers require(), module.exports, and the details that trip people up when switching between CommonJS and ES modules.

What Is CommonJS?

CommonJS is a module specification that predates native JavaScript modules. Node.js adopted it in 2009 because JavaScript itself had no built-in module system at the time. Every .js file in a CommonJS project is wrapped in a function that provides require, module, exports, __filename, and __dirname automatically.

You export values by assigning to module.exports (or adding properties to the shorthand exports object), and you consume them elsewhere with require(), which reads and executes the target file synchronously the first time, then returns a cached result on every subsequent call.

// logger.js
function log(message) {
  console.log(`[LOG] ${message}`);
}

module.exports = { log };

// index.js
const { log } = require('./logger.js');
log('Server starting...');

module.exports is the actual object returned by require(). Whatever you assign to it becomes the public interface of that file.

CommonJS Syntax

module.exports = value;          // export a single default value
exports.name = value;             // export a named property (shorthand)
const x = require('./file.js');   // import a local file
const pkg = require('some-pkg');  // import an installed package
  • module.exports replaces the entire export object; use it for a single default export.
  • exports is a shorthand reference to module.exports, don't reassign exports itself directly.
  • require() is synchronous, it blocks until the target module has fully loaded.
  • Circular require() calls are allowed but can return a partially-completed module.

CommonJS Cheatsheet

The essential CommonJS patterns you'll see in most existing Node.js codebases.

Pattern Example Notes
Default export module.exports = fn; One value represents the whole module
Named exports exports.a = 1; exports.b = 2; Multiple named values
Destructured import const { a, b } = require('./x'); Pull out specific named exports
Default import const fn = require('./x'); Use when the module exports one value
Built-in module const fs = require('node:fs'); node: prefix is optional but recommended
Current dir __dirname Absolute path to the current file's folder

The Module Wrapper Function

Node.js doesn't run your CommonJS file exactly as written, it wraps it in a function that supplies exports, require, module, __filename, and __dirname as parameters. This is why those identifiers are available without any import, and it's also why top-level this in a CommonJS file refers to module.exports, not the global object.

(function (exports, require, module, __filename, __dirname) {
  // your file's code runs here
});

Module Caching

The first time a file is required, Node.js executes it and caches the resulting module.exports object by resolved file path. Every subsequent require() for that same path returns the cached object instantly, without re-running the file's code.

  • Mutating an exported object in one place affects every other file that required it.
  • Caching is why singletons (like a shared database connection) are easy to build with plain require().
  • Clearing the cache manually (delete require.cache[...]) is rare and mostly used in testing.

Common Mistakes

  • Reassigning exports = { ... } directly instead of module.exports = { ... }, which silently breaks the export (the local exports variable is replaced, not the real export).
  • Forgetting that require() is synchronous and can block on large or deeply nested dependency trees.
  • Relying on load order in circular require() situations without understanding partial exports.
  • Using .js files with CommonJS syntax inside a package.json configured with "type": "module".

Key Takeaways

  • CommonJS is Node's original, synchronous module system using require() and module.exports.
  • module.exports and exports both point to the same object by default, don't reassign exports directly.
  • Required modules are cached by resolved file path after their first load.
  • CommonJS files automatically receive __dirname, __filename, require, and module from a wrapper function.

Pro Tip

If a value you exported seems to be missing halfway through a file, check for a circular require(), the two files may be requiring each other before either has finished defining its exports.