NodeJSIntroduction to Modules

Introduction to Modules

A module is a self-contained unit of code — usually a single file — that hides its internals and exposes only what it chooses. Modules are how Node turns thousands of small files into one coherent program: each file has its own scope, declares its dependencies explicitly, and shares a small public surface. Without them, every variable would collide in one global namespace. This page is the map; the following pages drill into each system.

Why modules exist

Browsers historically loaded scripts into one shared global scope — every var and function from every <script> lived together, fighting over names. Node rejected that from day one: each file is its own module with a private scope. The benefits compound as a project grows:

  • Encapsulation — a module exposes a deliberate API and hides the rest.

  • Explicit dependencies — you can see exactly what a file needs at the top.

  • Reusability — the same module is imported by many files, defined once.

  • No global pollution — names are local unless you export them (why that matters).

Two module systems in Node

Node supports two module systems, and you will encounter both. Knowing which one a file uses is the first thing to check when an import/require error appears:

CommonJS (CJS)

ES Modules (ESM)

Import

const x = require(...)

import x from ...

Export

module.exports = ...

export ...

Default extension

.cjs (or .js)

.mjs (or .js + "type":"module")

Loading

Synchronous

Asynchronous

Origin

Node's original system

The JavaScript standard

Top-level await

No

Yes

Which one runs?
For a `.js` file, Node decides by the nearest `package.json`: `"type": "module"` → ESM, otherwise CommonJS. `.mjs` is always ESM and `.cjs` is always CommonJS, regardless of `package.json`. Full breakdown in [CommonJS vs ES Modules](/nodejs/commonjs-vs-esm).
Three kinds of module

Regardless of system, the thing you import comes from one of three places. The specifier string tells Node which:

Kind

Specifier looks like

Example

Core (built-in)

A bare name, often node:-prefixed

require('node:fs')

Third-party

A bare name resolved in node_modules

require('express')

Local

A relative or absolute path

require('./utils')

We cover the distinction in depth in Core, Local & Third-Party Modules.

A first taste

math.js — the module

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

const PI = 3.14159   // private: never exported, invisible to importers

module.exports = { add }

app.js — the consumer

JS
const { add } = require('./math.js')

console.log(add(2, 3))   // 5
// console.log(PI)       // ReferenceError — PI never left math.js
5
The export is the contract
`add` is public because it was exported; `PI` is private because it was not. This single decision — what to put on `module.exports` — defines the module's entire API. Everything else is an implementation detail you are free to change.
What a module gives you for free

Inside any CommonJS file, Node injects a few variables you never declared — require, module, exports, __dirname, __filename. They are not global; they are parameters of a hidden wrapper function Node wraps every file in. That mechanism — and why __dirname differs per file — is explained in The Module Wrapper Function.

Next
Start with the system that powers most of the npm ecosystem: [CommonJS Modules (require)](/nodejs/commonjs).