Modules (CommonJS & ESM)

Reviewed & published by Brayan K

A module in Node.js is a self-contained file of code whose values you can share with other files — using CommonJS (require / module.exports) or modern ES Modules (import / export).

Part of the free Node.js course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.

By the end of this lesson you'll be able to split a program across several files, share functions between them with both module systems, pull in Node's built-in core modules, and recognise the small differences between named and default exports.

What You'll Learn in This Lesson

1️⃣ CommonJS: module.exports and require()

CommonJS is Node's original module system, and you'll still see it in the vast majority of existing projects. The idea is simple: one file attaches values to module.exports, and another file reads them back with require(). Here's a math.js file that exports two functions.

// math.js — a CommonJS module that EXPORTS two functions.
// Everything you attach to module.exports becomes available
// to any file that require()s this one.

function add(a, b) {
  return a + b;
}

function multiply(a, b) {
  return a * b;
}

// Share these two functions with the outside world.
module.exports = { add, multiply };

Now a second file, app.js, brings those functions in with require('./math'). The leading ./ is important: it tells Node "look for a local file", not an installed package.

// app.js — IMPORTS math.js with require().
// The './' means "a local file in THIS folder" (not an npm package).
// You can leave off the .js extension; Node adds it for you.
const math = require('./math');

console.log(math.add(2, 3));        // -> 5
console.log(math.multiply(4, 5));   // -> 20

// You can also pull out just the pieces you want with destructuring:
const { add } = require('./math');
console.log(add(10, 7));            // -> 17

2️⃣ ES Modules: export and import

ES Modules (ESM) is the official JavaScript standard, the same one browsers use. Instead of module.exports you write export, and instead of require() you write import. To switch a Node project to ESM you either add { "type": "module" } to your package.json or give files the .mjs extension.

// math.mjs (or math.js with "type": "module" in package.json)
// ES Modules use the 'export' keyword instead of module.exports.

// A named export — exported under its own name:
export function add(a, b) {
  return a + b;
}

// You can have many named exports per file:
export function multiply(a, b) {
  return a * b;
}

// A default export — the file's single "main" value:
export default function subtract(a, b) {
  return a - b;
}

Notice the two flavours of export above: named exports (add, multiply) and a single default export (subtract). When you import, the default comes with no braces and the named ones go inside { }. In ESM you must include the file extension on local imports.

// app.mjs — IMPORTS with the 'import' keyword.
// In ESM you MUST include the .mjs / .js extension on local files.
import subtract, { add, multiply } from './math.mjs';

console.log(add(2, 3));        // -> 5
console.log(multiply(4, 5));   // -> 20
console.log(subtract(9, 4));   // -> 5

// Tip: 'subtract' is the default export, so it has no braces.
// 'add' and 'multiply' are named exports, so they go in { }.
const total = add(1, 1);
console.log(`total = ${total}`); // -> total = 2

Your turn. The two files below work once you fill in the two blanks marked ___. Follow the 👉 hints, then run it and compare with the expected output.

// 🎯 YOUR TURN — two files, replace each ___ then run it.

// ===== greet.js =====
function sayHello(name) {
  return `Hello, ${name}!`;
}

// 1) Share sayHello so other files can use it:
module.___ = { sayHello };   // 👉 the property that exports values

// ===== app.js =====
// 2) Bring in greet.js (it's a LOCAL file in this folder):
const greet = ___('./greet');  // 👉 the CommonJS import function

console.log(greet.sayHello('Sam'));

// ✅ Expected output:
//    Hello, Sam!

3️⃣ Built-in Core Modules and node: Imports

Node ships with dozens of core modules — ready-made tools like path (file paths), os (operating-system info), fs (files), and http (web servers). You don't install them; they come with Node. The modern style writes the node: prefix (for example require('node:path')) so it's obvious you mean the built-in, not an npm package.

Whichever module system you use, it helps to keep the difference between named and default exports straight. Here's a side-by-side comparison.

// NAMED exports: many per file, imported by their exact name in { }.
//   export function add() {}      ->  import { add } from './math.js'
//
// DEFAULT export: one per file, you pick any name when importing.
//   export default function () {} ->  import anyName from './math.js'

// You can mix them in a single import line:
import subtract, { add, multiply } from './math.mjs';
//     ^default      ^named   ^named

// CommonJS has no 'default' keyword — module.exports = X is the
// closest equivalent, and require() simply returns whatever X is:
const everything = require('./math');   // -> { add, multiply }

Mini-Challenge: A Temperature Module

No blanks this time — just a brief and an outline to keep you on track. Write the two files yourself, run them, and check your output against the example in the comments. Splitting one job across two files is exactly the habit that scales to real projects.

// 🎯 MINI-CHALLENGE: a tiny "temperature" module (CommonJS)

// ===== temp.js =====
// 1. Write a function cToF(c) that returns c * 9 / 5 + 32.
// 2. Export it with module.exports so other files can require it.

// ===== app.js =====
// 3. require('./temp') and call cToF(100), then console.log the result.
//
// ✅ Expected output:
//    212

// your code here

Common Errors (and the fix)

Pro Tips

📋 Quick Reference — Modules

TaskSyntax
Export (CommonJS)module.exports = x
Import (CommonJS)const x = require('./x')
Export (ESM, default)export default x
Export (ESM, named)export { x }
Import (ESM)import x from './x.js'
Core modulerequire('node:path')
Enable ESM"type": "module"

🎉 Lesson Complete!

Practice quiz

In CommonJS, how do you export values from a file?

  • export default x
  • module.exports = x
  • exports x
  • share x

Answer: module.exports = x. CommonJS assigns what you want to share to module.exports.

Which function imports a module in CommonJS?

  • import()
  • include()
  • require()
  • load()

Answer: require(). require('./file') brings in a local CommonJS module.

What does the './' prefix in require('./math') mean?

  • A package from npm
  • A core built-in module
  • A global variable
  • A local file in the same folder

Answer: A local file in the same folder. './' tells Node to look for a local file, not an npm package.

Which keyword declares a NAMED export in ES modules?

  • export
  • module
  • share
  • public

Answer: export. ESM uses the export keyword for named exports.

How many default exports can a single ES module have?

  • Unlimited
  • Exactly one
  • Zero only
  • Two

Answer: Exactly one. A file can have many named exports but only one default export.

When importing a default export, named imports go in braces. How is the default written?

  • In square brackets
  • Without braces, with any name you choose
  • Always named 'default'
  • In double quotes

Answer: Without braces, with any name you choose. The default export is imported without braces and you may name it anything.

Which prefix makes it explicit you mean a built-in core module?

  • core:
  • lib:
  • std:
  • node:

Answer: node:. The node: prefix (e.g. require('node:path')) marks a built-in module.

What does package.json need so .js files are treated as ES modules?

  • "type": "module"
  • "esm": true
  • "module": "es"
  • "format": "esm"

Answer: "type": "module". Setting "type": "module" enables ESM for .js files.

In ESM, what is required on local file imports that CommonJS lets you omit?

  • The word import
  • Quotes
  • The file extension (.js / .mjs)
  • A leading slash

Answer: The file extension (.js / .mjs). ESM requires the explicit file extension on local imports.

What does require('./math') return when math.js does module.exports = { add, multiply }?

  • Only add
  • undefined
  • A Promise
  • The object { add, multiply }

Answer: The object { add, multiply }. require returns exactly whatever was assigned to module.exports.

Continue this course

Frequently asked questions

What's the difference between CommonJS and ES Modules?

CommonJS (CJS) is Node's original module system: you share values with module.exports and pull them in with require(), and it loads modules synchronously. ES Modules (ESM) is the official JavaScript standard used in browsers and modern Node: you share values with export and pull them in with import, and it supports features like top-level await and static analysis. CommonJS is still everywhere in existing Node code, while new projects increasingly choose ESM. Both work in current versions of Node.

How do I turn on ES Modules in a Node project?

You have two options. Either add "type": "module" to your package.json, which makes every .js file in the project an ES Module, or give individual files the .mjs extension. With "type": "module" set, you use import/export everywhere; if you still need a CommonJS file in that project, name it with the .cjs extension. By default (no "type" field) Node treats .js files as CommonJS.

Can I mix require() and import in the same file?

Not freely. A CommonJS file uses require()/module.exports; an ES Module uses import/export — you can't put a top-level import statement in a CommonJS file, and you can't use require() directly in an ES Module without a small workaround (createRequire). The simplest rule is: pick one system per file. ES Modules can import CommonJS packages, but the reverse (a require of an ESM-only package) needs dynamic import().

What's the difference between named exports and a default export?

A file can have many named exports, each imported by its exact name inside curly braces: import { add } from './math.js'. A file can have at most one default export, imported with no braces and any name you like: import anything from './math.js'. Named exports are great when a module offers several functions; a default export suits a module that has one obvious main thing.