Documentation Index
Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
Use this file to discover all available pages before exploring further.
Modules in Node.js
Modules are the building blocks of any Node.js application. Think of them like LEGO bricks: each module is a self-contained piece with a specific shape and purpose. You snap them together to build something larger, and you can swap one brick for another without rebuilding the whole structure. Without modules, you would end up with one massive file where every function can see and accidentally break every other function—the programming equivalent of storing your entire house in a single room. Node.js uses the CommonJS module system by default, though it also supports ES Modules (ESM) in newer versions.The require Function
To include a module, use the require() function with the name of the module.
Creating Custom Modules
Let’s create a simple module that exports some math functions. math.jsAlternative Export Syntax
You can also attach properties directly toexports (which is a shorthand for module.exports).
Module Wrapper Function
Under the hood, Node.js doesn’t execute your code directly. It wraps it inside a function wrapper:__filename and __dirname without declaring them—they are parameters injected by Node.js, not magic globals. Understanding this wrapper is key to understanding why var x = 5 in a module does not pollute the global scope the way it would in a browser script tag.
Built-in Modules
Node.js comes with many useful built-in modules. We will explore these in depth in later chapters, but here are a few common ones:fs: File system operations.http: Create HTTP servers.path: Utilities for working with file and directory paths.os: Operating system information.events: Event emitter.
Example: The os Module
ES Modules (import/export)
Node.js has added support for ES Modules, which is the standard in modern JavaScript (and browsers). To use ES Modules, you can either:- Use the
.mjsextension for your files. - Add
"type": "module"to yourpackage.json.
CommonJS vs ES Modules Comparison
| Feature | CommonJS | ES Modules |
|---|---|---|
| Syntax | require() / module.exports | import / export |
| Loading | Synchronous | Asynchronous |
| File Extension | .js or .cjs | .mjs or .js with “type”: “module” |
| Top-level await | Not supported | Supported |
| Dynamic imports | require(variable) | import(variable) |
| Tree shaking | Limited | Supported |
| Browser support | No | Yes |
__dirname / __filename | Available directly | Must use import.meta.url + fileURLToPath |
| Conditional exports | if (x) require('a') works anywhere | Static imports must be at top level; use dynamic import() for conditional loading |
| Circular dependency handling | Returns partially-loaded module | Throws ReferenceError if binding not yet initialized |
| Default export | module.exports = value | export default value |
Module System Decision Framework
Start a new project today? Use ES Modules ("type": "module" in package.json). The ecosystem is moving toward ESM, bundlers work better with it, and it aligns with browser JavaScript.
Maintaining an existing CommonJS project? Stay with CommonJS unless you have a compelling reason to migrate. Converting a large codebase is non-trivial: you need to update every require to import, handle __dirname replacements, deal with libraries that only ship CommonJS, and fix circular dependencies that worked under CJS but break under ESM.
Publishing an NPM package? Ship both formats using the exports field in package.json (conditional exports). This lets CJS and ESM consumers both use your package without issues.
Edge case — CJS/ESM interop: You can import a CommonJS module from ESM (Node.js wraps it as a default export). But you cannot require() an ES module from CommonJS — you must use await import() inside an async function instead. This asymmetry trips up many teams during migration.
Module Resolution: How Node.js Finds Your Modules
When you callrequire('something'), Node.js follows a specific resolution algorithm. Understanding it prevents mysterious “module not found” errors:
| What you write | What Node.js does | Example |
|---|---|---|
require('./foo') | Looks for ./foo.js, then ./foo/index.js in the same directory | Local module |
require('fs') | Checks built-in modules first (always wins over NPM packages with same name) | Built-in module |
require('lodash') | Walks up the directory tree checking node_modules/ at each level | NPM package |
require('/abs/path') | Uses the absolute path directly | Absolute path |
node_modules crawl: If your project is at /app/src/utils/helper.js and you require('lodash'), Node.js checks: /app/src/utils/node_modules/lodash, then /app/src/node_modules/lodash, then /app/node_modules/lodash, then /node_modules/lodash. This crawl is why deeply nested node_modules caused performance problems in early Node.js and why tools like pnpm use symlinks instead.
Module Caching
Node.js caches modules after the firstrequire(). Subsequent calls return the cached version.
Circular Dependencies
Node.js handles circular dependencies, but they can cause issues.The path Module
The path module provides utilities for working with file and directory paths.
The url Module
Creating a Reusable Module
Let’s create a practical utility module:Summary
- Node.js uses CommonJS (
require/module.exports) by default - ES Modules (
import/export) are also supported and are becoming the standard for new projects - Modules are cached after first load—making every stateful module an accidental singleton
- Avoid circular dependencies by extracting shared logic into a separate module
- Use the
pathmodule for cross-platform file paths (never concatenate paths with string+) - Use the
urlmodule for URL parsing and manipulation - Node.js wraps every module in a wrapper function, providing local scope and variables like
__dirname - When starting a new project, prefer ES Modules (
"type": "module"in package.json) for tree-shaking support and alignment with the broader JavaScript ecosystem