02 - Modules
📋 Jump to TakeawaysWhy Modules
Without modules, everything lives in one file or leaks into global scope. Modules let you split code into files and control what's shared between them.
Node.js has two module systems: CommonJS (the original) and ES Modules (the standard). You'll see both in the wild.
CommonJS (require/module.exports)
This is what Node.js used from the start. Still the default.
// math.js
function add(a, b) {
return a + b;
}
function multiply(a, b) {
return a * b;
}
module.exports = { add, multiply };// app.js
const { add, multiply } = require('./math');
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20require is synchronous. It reads the file, executes it, and returns whatever module.exports is set to.
Exporting a Single Thing
If your module does one thing, export it directly.
// logger.js
module.exports = function log(msg) {
console.log(`[${new Date().toISOString()}] ${msg}`);
};// app.js
const log = require('./logger');
log('Server started'); // [2026-04-02T10:30:00.000Z] Server startedES Modules (import/export)
The standard JavaScript module system. Works in Node.js 14+ with either a .mjs extension or "type": "module" in package.json.
If you don't have a package.json yet, run npm init -y to create one, then add "type": "module" to it. We'll cover npm in detail in the next lesson.
// math.mjs
export function add(a, b) {
return a + b;
}
export function multiply(a, b) {
return a * b;
}// app.mjs
import { add, multiply } from './math.mjs';
console.log(add(2, 3)); // 5
console.log(multiply(4, 5)); // 20Default Exports
// logger.mjs
export default function log(msg) {
console.log(`[${new Date().toISOString()}] ${msg}`);
}// app.mjs
import log from './logger.mjs';
log('Server started');CommonJS vs ESM
| Feature | CommonJS | ES Modules |
|---|---|---|
| Syntax | require() / module.exports |
import / export |
| Loading | Synchronous | Asynchronous |
| File extension | .js (default) |
.mjs or "type": "module" |
| Top-level await | No | Yes |
__dirname |
Available | Use import.meta.dirname (Node 21+) |
You can't mix them in the same file. A file is either CommonJS or ESM, not both. If your package.json has "type": "module", all .js files are ESM. Use .cjs for any file that needs CommonJS in an ESM project, or .mjs for ESM files in a CommonJS project.
// ❌ This does NOT work in the same file
const fs = require('fs');
import path from 'path';
// ✅ Pick one system per file
// CommonJS file:
const fs = require('fs');
const path = require('path');
// ESM file:
import fs from 'node:fs';
import path from 'node:path';Which Should I Use?
Use ESM for new projects. It's the JavaScript standard, works in browsers too, and supports top-level await.
You'll still see CommonJS everywhere though. Most existing npm packages and older projects use it. Config files like jest.config.js or .eslintrc.js often expect it too. You need to know both, but default to ESM when starting fresh.
Built-in Modules
Node.js comes with modules you don't need to install.
In CommonJS projects (the default):
const fs = require('fs'); // File system
const path = require('path'); // Path utilities
const os = require('os'); // Operating system info
const http = require('http'); // HTTP server/client
const crypto = require('crypto'); // CryptographyIn ESM projects ("type": "module" in package.json):
import fs from 'node:fs';
import path from 'node:path';The node: prefix makes it clear you're importing a built-in, not a package from node_modules. Remember, import only works in ESM files. If your project is CommonJS, stick with require().
Module Resolution
When you require('something'), Node looks in this order:
- Built-in modules (
fs,path,http) node_modules/something(walks up directories until found)- Relative path if it starts with
./or../
require('fs'); // Built-in
require('express'); // node_modules/express
require('./utils'); // ./utils.js in same directory
require('../config'); // ../config.js one level upKey Takeaways
- CommonJS uses
require()andmodule.exports, it's synchronous - ES Modules use
import/export, support top-level await - Use
.mjsextension or"type": "module"in package.json for ESM - Built-in modules don't need installation:
fs,path,os,http - Prefix built-ins with
node:in ESM for clarity:import fs from 'node:fs' - Node resolves modules: built-ins first, then node_modules, then relative paths