# Modules in Node.js

Course: [Node.js](https://www.devtutorials.ai/learn/nodejs) · Section: Node.js Foundations
Source: https://www.devtutorials.ai/learn/nodejs/foundations/modules-in-nodejs

> What a module is, module scope, CommonJS with require and module.exports, the module wrapper, how Node.js finds modules, caching and circular imports, JSON files, ES modules with import and export, import.meta, dynamic import and top-level await, using CommonJS and ES modules together, and running TypeScript files.

## Modules in Node.js

**Module** = one file of code that shares only what it chooses to share.

- A big program in one file is hard to read.
- Node.js lets you split it into many small files.
- Each file is a module.
- A module **exports** the things others may use.
- Another module **imports** them.

**Simple lesson:** a module is a file with its own private space and a small public door.

### A Real-World Example

Think of the Greenfield School office.

- The marks clerk keeps the marks register.
- The fees clerk keeps the fees register.
- Each clerk works in a separate room.
- When the principal needs a report, she asks each clerk for a result.
- Nobody walks into another room and changes the register.

In Node.js, `marks.js` and `fees.js` are the two rooms. `report.js` asks them for results.

You met `import` and `export` in **Import and Export** in the JavaScript course. This lesson shows how Node.js runs them, and the older `require` style that many Node.js projects still use.

## What You Will Learn

In this lesson, you will learn:

1. What a module is and why it has its own scope
2. CommonJS: `require()`, `module.exports` and `exports`
3. The module wrapper, `__dirname` and `require.main`
4. How Node.js finds a module, and the `node:` prefix
5. Module caching, circular imports and JSON files
6. ES modules: `import`, `export`, `"type"`, `.mjs` and `.cjs`
7. `import.meta`, dynamic `import()` and top-level `await`
8. Using CommonJS and ES modules together
9. Running TypeScript files with Node.js

## Module Fundamentals

### Module Scope

**Module scope** = names made in a file stay inside that file.

- A `const` in `marks.js` is not visible in `report.js`.
- Two files can both have a variable called `total`. They never clash.
- Only exported things can be used by other files.

### Three Kinds of Modules

- **Built-in modules** come with Node.js, such as `node:fs` and `node:path`.
- **File modules** are your own files, such as `./marks.js`.
- **Package modules** are downloaded with npm into `node_modules`.

**Simple lesson:** every file is private by default, and it shares only what it exports.

## CommonJS: require and module.exports

**CommonJS** = the first module system of Node.js. It uses `require()` and `module.exports`.

- `module.exports` = the thing a file gives to others.
- `require("./marks")` = load that file and get its `module.exports`.
- CommonJS is used when a file ends in `.cjs`, or when the project has no `"type": "module"` setting.
- Many older packages and tutorials still use it.

Click **▶ Run** and read the terminal.

- **First line:** `grades.js` ran first, because `require` loads it before `app.js` goes on.
- **No `.js` needed:** `require("./grades")` found `grades.js` by itself.
- This lab runs JavaScript only. CommonJS is a JavaScript feature, so the TypeScript menu shows the same files.

### module.exports vs exports

- `exports` is a short name that points to `module.exports` at the start.
- `exports.average = ...` adds to the same object. That works.
- `exports = { ... }` makes `exports` point somewhere new. Node.js never sees it.
- Node.js always returns `module.exports`, not `exports`.

Never write `exports = ...`. Write `module.exports = ...`, or add names with `exports.name = ...`.

**Simple lesson:** use `module.exports = ...` to share one thing, or `exports.name = ...` to add names.

## The Module Wrapper

Before running a CommonJS file, Node.js wraps it in a function.

```js
(function (exports, require, module, __filename, __dirname) {
  // your file's code goes here
});
```

<figure className="ngc-fig node-fig">
<svg viewBox="0 0 560 220" role="img" ariaLabelledby="fig-node-wrapper" style={{ width: "100%", maxWidth: 560, height: "auto" }}>
<title id="fig-node-wrapper">Animation: Node.js wraps a file in a function that gives it exports, require, module, __filename and __dirname</title>
<rect x="150" y="70" width="260" height="90" rx="10"  style={{ fill: "var(--ngc-orange-soft)", stroke: "var(--ngc-orange)", strokeWidth: 1.5 }}/>
<text x="280" y="105" textAnchor="middle" style={{ fill: "var(--ngc-orange)", fontFamily: "var(--sans)", fontSize: 13, fontWeight: 700 }}>grades.js</text>
<text x="280" y="128" textAnchor="middle" style={{ fill: "var(--ink)", fontFamily: "var(--mono)", fontSize: 12 }}>function average(list) ...</text>
<rect x="110" y="30" width="340" height="170" rx="14" opacity="0" style={{ fill: "none", stroke: "var(--ngc-purple)", strokeWidth: 2.5, strokeDasharray: "8 6" }}>
<animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.2;0.3;0.9;1" dur="6s" repeatCount="indefinite" />
</rect>
<g opacity="0">
<animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.35;0.45;0.9;1" dur="6s" repeatCount="indefinite" />
<text x="280" y="52" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontFamily: "var(--mono)", fontSize: 12, fontWeight: 700 }}>function (exports, require, module, __filename, __dirname)</text>
<text x="280" y="190" textAnchor="middle" style={{ fill: "var(--ngc-purple)", fontFamily: "var(--sans)", fontSize: 12 }}>the file's own private room</text>
</g>
<g opacity="0">
<animate attributeName="opacity" values="0;0;1;1;0" keyTimes="0;0.55;0.65;0.9;1" dur="6s" repeatCount="indefinite" />
<text x="20" y="115" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 12, fontWeight: 700 }}>gets tools</text>
<text x="470" y="115" style={{ fill: "var(--ngc-blue)", fontFamily: "var(--sans)", fontSize: 12, fontWeight: 700 }}>gives exports</text>
</g>
</svg>
<figcaption>The wrapper gives each file its own scope and five ready names.</figcaption>
</figure>

- **The wrapper makes module scope.** Variables inside a function are private.
- **`__filename`** = the full path of this file.
- **`__dirname`** = the folder this file is in.
- **`require.main === module`** = true only for the file you ran with `node`.

- `tools/report.js` says **false**, because `app.js` was the file started with `node`.
- Use `require.main === module` to run code only when a file is started directly.

**Simple lesson:** the wrapper gives every CommonJS file a private room and five free names.

## How Node.js Finds a Module

**Specifier** = the text inside `require("...")` or `import ... from "..."`.

- **Relative specifier:** starts with `./`, `../` or `/`. It points to your own file.
- **Bare specifier:** a plain name like `"lodash"`. It points to a package or a built-in module.
- **`node:` prefix:** `"node:fs"` always means the built-in module, never a package with the same name.

For a bare name, Node.js looks in `node_modules` folders, going up one folder at a time.

<figure className="ngc-fig node-fig">
<svg viewBox="0 0 560 230" role="img" ariaLabelledby="fig-node-resolve" style={{ width: "100%", maxWidth: 560, height: "auto" }}>
<title id="fig-node-resolve">Animation: require("shout") looks in node_modules of the current folder, then the parent folder, until it finds the package</title>
<rect x="30" y="20" width="230" height="40" rx="8" opacity="0.12"  style={{ fill: "var(--ngc-slate)", stroke: "var(--ngc-slate)" }}/>
<text x="45" y="45" textAnchor="start" style={{ fill: "var(--ink)", fontFamily: "var(--mono)", fontSize: 12 }}>/project/src/app.js</text>
<rect x="30" y="80" width="230" height="40" rx="8"  style={{ fill: "var(--ngc-red-soft)", stroke: "var(--ngc-red)" }}/>
<text x="45" y="105" textAnchor="start" style={{ fill: "var(--ink)", fontFamily: "var(--mono)", fontSize: 12 }}>/project/src/node_modules</text>
<text x="280" y="105" textAnchor="start" style={{ fill: "var(--ngc-red)", fontFamily: "var(--sans)", fontSize: 12, fontWeight: 700 }}>not here</text>
<rect x="30" y="140" width="230" height="40" rx="8"  style={{ fill: "var(--ngc-green-soft)", stroke: "var(--ngc-green)" }}/>
<text x="45" y="165" textAnchor="start" style={{ fill: "var(--ink)", fontFamily: "var(--mono)", fontSize: 12 }}>/project/node_modules/shout</text>
<text x="280" y="165" textAnchor="start" style={{ fill: "var(--ngc-green)", fontFamily: "var(--sans)", fontSize: 12, fontWeight: 700 }}>found: read package.json "main"</text>
<circle cx="270" cy="40" r="7" style={{ fill: "var(--ngc-orange)" }}>
<animate attributeName="cy" values="40;100;100;160;160" keyTimes="0;0.25;0.45;0.7;1" dur="5s" repeatCount="indefinite" />
</circle>
<text x="280" y="215" textAnchor="middle" style={{ fill: "var(--muted)", fontFamily: "var(--sans)", fontSize: 12 }}>Not found anywhere: Error Cannot find module</text>
</svg>
<figcaption>Bare names are searched in node_modules, from the current folder upwards.</figcaption>
</figure>

### Files, Folders and main

- `require("./grades")` tries `grades`, then `grades.js`, then `grades.json`.
- `require("./utils")` on a folder tries `utils/package.json` and its `"main"` field.
- If there is no `"main"`, it tries `utils/index.js`.
- These guesses work in CommonJS only. ES modules need the full file name.

- `"main": "lib/shout.js"` told Node.js which file is the entry of the package.
- In a real project, `npm install` fills `node_modules` for you. The **Packages and npm** lesson shows how.

**Simple lesson:** `./` means your file, a bare name means a package, and `node:` means built-in.

## Module Loading: Cache, Cycles and JSON

### Module Caching

**Module cache** = Node.js runs a module once and keeps its exports.

- The second `require` of the same file returns the same object.
- The file's code does not run again.
- `require.cache` holds every loaded module. Deleting an entry forces a reload, which is rarely needed.

### Circular Dependencies

**Circular dependency** = file A needs file B, and file B needs file A.

- Node.js does not get stuck.
- The second file gets the **unfinished** exports of the first file.
- This often gives `undefined` values. Move shared code to a third file to fix it.

- **Cache:** `counter loaded` printed once, and both names point to one object.
- **Cycle:** `b.js` saw `a.done = false`, because `a.js` had not finished yet.

### Loading JSON Files

- `require("./data.json")` reads the file and turns it into an object.
- It is cached like any module.
- In ES modules, use `import data from "./data.json" with { type: "json" }`.

**Simple lesson:** a module runs once, cycles see half-made exports, and JSON loads as an object.

## ES Modules in Node.js

**ES module (ESM)** = the standard JavaScript module system, with `import` and `export`.

Node.js treats a file as an ES module when:

- the file ends in `.mjs`, or
- the nearest `package.json` has `"type": "module"`, or
- the file has `import` or `export` and no setting says otherwise.

A file ending in `.cjs` is always CommonJS.

<div className="tscroll">

| Topic | CommonJS | ES modules |
|---|---|---|
| Load a module | `const x = require("./x")` | `import x from "./x.js"` |
| Share | `module.exports = ...` | `export` and `export default` |
| File extension in paths | Optional | Required |
| Folder and file names | `__dirname`, `__filename` | `import.meta.dirname`, `import.meta.filename` |
| Loads | Step by step, while running | All imports first, then the code |
| `await` at the top of a file | Not allowed | Allowed |

</div>

Click **▶ Run**, then switch the **Language** menu and run it again.

- **JavaScript:** the import says `./grades.js`. The full file name is required.
- **TypeScript:** the import says `./grades.ts`. Node.js runs `.ts` files and needs the real extension.
- **`import.meta.filename`** = this file's full path. It replaces `__filename` in ES modules.

**Simple lesson:** ES modules use `import` and `export`, and always need the full file name.

## import.meta, Dynamic import() and Top-Level await

- **`import.meta.dirname`** = the folder of this file.
- **`import.meta.filename`** = the full path of this file.
- **`import.meta.url`** = the same path as a `file://` web address.
- **Dynamic `import()`** = load a module later, only when you need it. It gives back a promise.
- **Top-level `await`** = `await` at the top of an ES module, outside any function.

### JSON Import Attributes

- `with { type: "json" }` is called an **import attribute**.
- Without it, Node.js stops with `ERR_IMPORT_ATTRIBUTE_MISSING`.
- It is a safety rule. A file you expect to be data can never run as code.

**Simple lesson:** `import.meta` tells a module where it lives, and `import()` loads code only when needed.

## Using CommonJS and ES Modules Together (Advanced)

- **ES module imports CommonJS:** works. `module.exports` becomes the default import.
- **CommonJS requires an ES module:** works in recent Node.js versions, if the ES module has no top-level `await`.
- **`createRequire(import.meta.url)`** = make a `require` function inside an ES module.

For a new project, pick ES modules and add `"type": "module"` to `package.json`. Use CommonJS when you work on an older project that already uses it.

**Simple lesson:** the two systems can work together, but pick one style for your own new code.

## Running TypeScript Files

Recent Node.js versions can run `.ts` files directly.

- Node.js **strips the types**. It removes the type notes and runs the JavaScript that is left.
- It does **not** check the types. Use `tsc --noEmit` for that (see **Configuring** in the TypeScript course).
- Imports must use the real extension, such as `./grades.ts`.
- A few TypeScript features need more than stripping, such as `enum`. They stop with an error.
- `npm install -D @types/node` gives your editor the types of `process`, `fs` and the other Node.js modules.
- On older Node.js versions, the `tsx` package runs `.ts` files: `npx tsx app.ts`.

```ts
// main.ts: runs with "node main.ts"
type Student = { name: string; marks: number };

const asha: Student = { name: "Asha", marks: 91 };
console.log(`${asha.name} scored ${asha.marks}`);
```

Output:

```text
Asha scored 91
```

```ts
// main.ts: enum needs more than type stripping
enum Grade { A, B }
console.log(Grade.A);
```

Output:

```text
/home/asha/school/main.ts:2
// main.ts: enum needs more than type stripping
enum Grade { A, B }
^^^^^^^^^^^^^^^^^^^
console.log(Grade.A);

SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: TypeScript enum is not supported in strip-only mode {
  code: 'ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX'
}

Node.js v22.20.0
```

**Simple lesson:** `node app.ts` runs TypeScript by removing the types, so keep the types simple.

## Common mistakes

<div className="tscroll">

| Mistake | Wrong idea | Correct idea | What goes wrong |
|---|---|---|---|
| `exports = { ... }` | It replaces the exports | Use `module.exports = { ... }` | The other file gets an empty object `{}` |
| `import x from "./grades"` in ESM | ESM guesses `.js` like `require` | Write the full name `./grades.js` | `ERR_MODULE_NOT_FOUND` |
| Using `require` in an ES module | Both work everywhere | Use `import`, or `createRequire` | `ReferenceError: require is not defined` |
| Using `__dirname` in an ES module | It exists in every file | Use `import.meta.dirname` | `ReferenceError: __dirname is not defined` |
| Importing JSON without `with { type: "json" }` | JSON imports like code | Add the import attribute | `ERR_IMPORT_ATTRIBUTE_MISSING` |
| Two files that require each other | Node.js waits for both | Move shared code to a third file | Values are `undefined` |
| `enum` in a `.ts` file run by `node` | Node.js runs all TypeScript | Use a union type or an object | `ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX` |

</div>

## TL;DR

1. **Module** = one file with its own scope. It shares only its exports.
2. **CommonJS** = `require()` and `module.exports`. Used for `.cjs` and older projects.
3. **Module wrapper** = the function Node.js wraps around a CommonJS file. It gives `__dirname` and `require`.
4. **Resolution** = how Node.js finds a module.
   - `./x` your file, `x` a package in `node_modules`, `node:x` built-in
5. **Cache** = a module runs once. Later loads get the same exports.
6. **ES module** = `import` and `export`. Used for `.mjs` and `"type": "module"`.
7. **`import.meta.dirname`** = the folder of an ES module.
8. **Dynamic `import()`** = load a module later. It returns a promise.
9. **Type stripping** = `node app.ts` removes types and runs the rest.

## Check Yourself

## Exercises

## What to Remember

- Every file is a module with its own private scope.
- CommonJS uses `require` and `module.exports`. Never assign to `exports` directly.
- `./` is your file, a bare name is a package, `node:` is built-in.
- A module runs once. Later loads share the same exports.
- ES modules use `import` and `export`, need full file names and use `import.meta.dirname`.
- `node app.ts` runs TypeScript by stripping types. It does not check them.

Next: **Packages and npm**. You will install other people's modules and run your project with scripts.
