02 - Modules

📋 Jump to Takeaways

Why 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)); // 20

require 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 started

ES 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)); // 20

Default 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');   // Cryptography

In 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:

  1. Built-in modules (fs, path, http)
  2. node_modules/something (walks up directories until found)
  3. 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 up

Key Takeaways

  • CommonJS uses require() and module.exports, it's synchronous
  • ES Modules use import/export, support top-level await
  • Use .mjs extension 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

📝 Ready to test your knowledge?

Answer the quiz below to mark this lesson complete.

© 2026 ByteLearn.dev. Free courses for developers. · Privacy