Modules in Node.js
By Syncfusion®, Inc. · Last updated
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:
- What a module is and why it has its own scope
- CommonJS:
require(),module.exportsandexports - The module wrapper,
__dirnameandrequire.main - How Node.js finds a module, and the
node:prefix - Module caching, circular imports and JSON files
- ES modules:
import,export,"type",.mjsand.cjs import.meta, dynamicimport()and top-levelawait- Using CommonJS and ES modules together
- Running TypeScript files with Node.js
Module Fundamentals
Module Scope
Module scope = names made in a file stay inside that file.
- A
constinmarks.jsis not visible inreport.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:fsandnode: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 itsmodule.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.jsran first, becauserequireloads it beforeapp.jsgoes on. - No
.jsneeded:require("./grades")foundgrades.jsby itself. - This lab runs JavaScript only. CommonJS is a JavaScript feature, so the TypeScript menu shows the same files.
module.exports vs exports
exportsis a short name that points tomodule.exportsat the start.exports.average = ...adds to the same object. That works.exports = { ... }makesexportspoint somewhere new. Node.js never sees it.- Node.js always returns
module.exports, notexports.
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.
(function (exports, require, module, __filename, __dirname) {
// your file's code goes here
});
Animation: Node.js wraps a file in a function that gives it exports, require, module, __filename and __dirname
grades.js function average(list) ...
function (exports, require, module, __filename, __dirname) the file's own private room
gets tools gives exports
The wrapper gives each file its own scope and five ready names.
- 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 withnode.
tools/report.jssays false, becauseapp.jswas the file started withnode.- Use
require.main === moduleto 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.
Animation: require("shout") looks in node_modules of the current folder, then the parent folder, until it finds the package
/project/src/app.js
/project/src/node_modules not here
/project/node_modules/shout found: read package.json "main"
Not found anywhere: Error Cannot find module
Bare names are searched in node_modules, from the current folder upwards.
Files, Folders and main
require("./grades")triesgrades, thengrades.js, thengrades.json.require("./utils")on a folder triesutils/package.jsonand its"main"field.- If there is no
"main", it triesutils/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 installfillsnode_modulesfor 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
requireof the same file returns the same object. - The file's code does not run again.
require.cacheholds 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
undefinedvalues. Move shared code to a third file to fix it.
- Cache:
counter loadedprinted once, and both names point to one object. - Cycle:
b.jssawa.done = false, becausea.jshad 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.jsonhas"type": "module", or - the file has
importorexportand no setting says otherwise.
A file ending in .cjs is always CommonJS.
| 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 |
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.tsfiles and needs the real extension. import.meta.filename= this file's full path. It replaces__filenamein 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 afile://web address.- Dynamic
import()= load a module later, only when you need it. It gives back a promise. - Top-level
await=awaitat 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.exportsbecomes 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 arequirefunction 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 --noEmitfor 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/nodegives your editor the types ofprocess,fsand the other Node.js modules.- On older Node.js versions, the
tsxpackage runs.tsfiles:npx tsx app.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:
Asha scored 91
// main.ts: enum needs more than type stripping
enum Grade { A, B }
console.log(Grade.A);
Output:
/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
| 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 |
TL;DR
- Module = one file with its own scope. It shares only its exports.
- CommonJS =
require()andmodule.exports. Used for.cjsand older projects. - Module wrapper = the function Node.js wraps around a CommonJS file. It gives
__dirnameandrequire. Resolution = how Node.js finds a module.
./xyour file,xa package innode_modules,node:xbuilt-in
- Cache = a module runs once. Later loads get the same exports.
- ES module =
importandexport. Used for.mjsand"type": "module". import.meta.dirname= the folder of an ES module.- Dynamic
import()= load a module later. It returns a promise. - Type stripping =
node app.tsremoves types and runs the rest.
Check Yourself
Exercises
What to Remember
- Every file is a module with its own private scope.
- CommonJS uses
requireandmodule.exports. Never assign toexportsdirectly. ./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
importandexport, need full file names and useimport.meta.dirname. node app.tsruns 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.