Before the JavaScript community agreed on a standard moduleWhat is module?A self-contained file of code with its own scope that explicitly exports values for other files to import, preventing name collisions. format, Node.js shipped with its own system called CommonJSWhat is commonjs?Node.js's original module format using require() and module.exports - distinct from and predating the ES module import/export syntax.. If you have ever looked at an older Express app or a Node.js tutorial from before 2020, you have seen it: require() at the top of files and module.exports at the bottom. Even today, the majority of npm packages are distributed in CommonJS format, so understanding it is not optional, it is table stakes for working in the Node.js ecosystem.
How require() works
When Node.js encounters require('something'), it goes through a resolution process to figure out what file or package to load. The three categories it checks are built-in modules, relative file paths, and installed packages from node_modules.
Built-in modules
Node.js ships with a standard libraryWhat is standard library?A collection of ready-made tools that come built into a language - no install required. Covers common tasks like reading files or making web requests.. You access it by passing the moduleWhat is module?A self-contained file of code with its own scope that explicitly exports values for other files to import, preventing name collisions. name as a string, no installation needed:
const fs = require('fs');
const path = require('path');
const http = require('http');
const os = require('os');These are always resolved first, before Node even looks at your file system.
Installed npm packages
When you npm install express, the package lands in node_modules/. You import it by name, and Node walks up the directory tree until it finds a matching folder:
const express = require('express');
const lodash = require('lodash');Your own files
Anything starting with ./ or ../ is treated as a relative path. You can omit the extension and Node will try several candidates in order:
// Imports ./utils.js, or ./utils/index.js if that fails
const utils = require('./utils');
// Imports exactly this file
const math = require('./helpers/math');
// Goes up one directory
const config = require('../config');require('./utils'), Node tries utils.js, then utils.json, then utils/index.js, then utils/index.json, then utils/package.json with a main field. Understanding this order prevents mysterious "module not found" errors when you forget an extension.Exporting from a moduleWhat is module?A self-contained file of code with its own scope that explicitly exports values for other files to import, preventing name collisions.
The object at module.exports is exactly what callers receive. You can put anything there: a plain object, a single function, a class, or a primitive value.
Exporting an object of named values
The most common pattern is exporting multiple named functions or constants:
// math.js
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
module.exports = {
add,
multiply,
PI: 3.14159
};// app.js
const { add, multiply } = require('./math');
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20Exporting a single class or function
When your module represents one thing, a class, a factory, a single utility function, set module.exports to that thing directly:
// Logger.js
class Logger {
constructor(name) {
this.name = name;
}
log(message) {
console.log(`[${this.name}] ${message}`);
}
error(message) {
console.error(`[${this.name}] ERROR: ${message}`);
}
}
module.exports = Logger;// app.js
const Logger = require('./Logger');
const logger = new Logger('App');
logger.log('Server starting...');Notice that when a caller imports your module, they choose the local variable name freely. That is one advantage of a single default-style export.
ModuleWhat is module?A self-contained file of code with its own scope that explicitly exports values for other files to import, preventing name collisions. caching
The first time you require() a module, Node loads, executes, and caches the result. Every subsequent call with the same path returns the cached export without re-executing the file. This is why modules behave like singletons, state set inside a module persists across all files that import it.
// Both variables point to the exact same object in memory
const utils1 = require('./utils');
const utils2 = require('./utils');
console.log(utils1 === utils2); // trueThis is a feature, not a bug. It lets you share a single database connection or configuration object across your entire application. However, it also means tests can end up sharing state between runs if you are not careful about resetting module state.
The exports shorthand
exports is a pre-created reference to module.exports. You can add properties to it one by one, but you must never reassign exports itself:
// This works - adding properties to the shared object
exports.add = (a, b) => a + b;
exports.multiply = (a, b) => a * b;
// This BREAKS - you're replacing the reference, not the object
exports = { add, multiply }; // Callers still see the original empty objectWhen in doubt, use module.exports directly. It is explicit and predictable.
Debugging moduleWhat is module?A self-contained file of code with its own scope that explicitly exports values for other files to import, preventing name collisions. resolution
If you are unsure where a module is being loaded from, require.resolve() returns the absolute file path without actually loading the module:
console.log(require.resolve('express'));
// /Users/you/project/node_modules/express/index.jsThis is useful for diagnosing version conflicts or shadowed packages in monorepoWhat is monorepo?A single repository that contains multiple related projects or packages, managed together with shared tooling. setups.
Quick reference
| Syntax | What it does |
|---|---|
require('fs') | Load a built-in Node.js module |
require('express') | Load an installed npm package |
require('./utils') | Load a local file (relative path) |
module.exports = value | Set what your module exports |
exports.name = value | Add a named property to exports |
require.resolve('pkg') | Get the absolute path of a module |
require.cache | Inspect the module cache |