Grokking Node.js
Vote

0% completed

​

Introduction to Node.js Modules

Why Split Code into Modules

A CommonJS Module

The Same Module as an ECMAScript Module

Telling Node.js Which System a File Uses

How the Two Systems Differ

Which One to Use

How does code in one file use a function that is written in another file? In a browser, every script on a page used to share one global scope, so a function defined in one script was simply visible in the next. Node.js does not work that way. Each file is a separate unit with its own variables, and it shares only what it chooses to export.

A file that exports values for other files to use is called a module. This chapter covers modules first, then the modules that come built into Node.js, files, and the process object. This lesson builds one small module twice, once in each of the two module systems that Node.js supports, and compares the results.

Why Split Code into Modules

A program that lives in one file becomes hard to read as it grows, and two parts of it can overwrite each other's variables by accident. Modules solve both problems. Each file holds one job, and nothing leaks out of it unless the file exports it.

Node.js supports two module systems. CommonJS, often shortened to CJS, is the system that Node.js used from its beginning, and most existing Node.js code and many npm packages use it. ECMAScript modules, shortened to ESM, are the standard module system of the JavaScript language, and they are the same import and export syntax that browsers use.

A CommonJS Module

The running example is a module with one function that builds a greeting. Create a folder for it, and inside the folder create a file named greet.js:

// greet.js function greet(name) { return `Hello, ${name}! Welcome to Node.js modules.`; } module.exports = greet;

Every CommonJS file has an object called module, and whatever you assign to module.exports is what other files receive. Here the export is the greet function itself. Any other function or variable in greet.js would stay private to the file.

Now create app.js in the same folder, which loads the module with require:

// app.js const greet = require('./greet'); console.log(greet('Alice'));

Run it from that folder:

node app.js
Hello, Alice! Welcome to Node.js modules.

The steps happen in this order. require('./greet') finds greet.js in the same folder, adding the .js extension itself. It runs that file from top to bottom, takes the value of its module.exports, and returns it. The ./ at the start tells Node.js that the module is a file in your project, and ../ works the same way for the parent folder. A name without either, such as require('fs'), means a built-in module or an installed package.

Node.js runs each module only once. If ten files call require('./greet'), the first call runs greet.js, and the other nine receive the same exported function from a cache. A module that holds data, such as a counter, is therefore shared by every file that loads it.

The Same Module as an ECMAScript Module

The ESM version of the same module marks what it shares with the export keyword:

// greet.js export function greet(name) { return `Hello, ${name}! Welcome to Node.js modules.`; }

The other file loads it with an import statement:

// app.js import { greet } from './greet.js'; console.log(greet('Alice'));

The output is the same as before. Two details differ from the CommonJS version. The function is a named export, so the import names it inside braces. The import path also ends in .js, because ESM does not add a missing extension. Without it, Node.js stops with the error ERR_MODULE_NOT_FOUND and asks Did you mean to import "./greet.js"?.

Telling Node.js Which System a File Uses

Node.js decides how to treat a .js file by looking at the nearest package.json file in its folder or a parent folder. A "type": "module" field there makes every .js file an ES module, and "type": "commonjs" makes every .js file a CommonJS module.

This matters as soon as you create a project with npm. Since npm 11, the version that comes with Node.js 24, npm init -y writes "type": "commonjs" into the package.json that it creates. Earlier versions of npm leave the field out. In such a project, the ESM version of app.js fails before it runs:

SyntaxError: Cannot use import statement outside a module

Change the field to "type": "module", and the same file runs. When a project has no type field at all, Node.js 22.7 and later look at the file itself. They first try to run it as CommonJS, and they run it again as an ES module if it contains import or export. If a package.json exists without the field, Node.js 24 also prints a MODULE_TYPELESS_PACKAGE_JSON warning that this second attempt costs time. Setting the field explicitly avoids the warning, and it tells every reader of the project which system it uses.

Two file extensions override the type field for a single file. A .mjs file is always an ES module, and a .cjs file is always a CommonJS module.

How the Two Systems Differ

When modules load. require is an ordinary function call. It loads the module at the moment that line runs, so you can call it inside an if block. An import statement is read before any code in the file runs, so all imports are loaded first, and they must appear at the top level of the file.

Waiting at the top level. An ES module can use await outside any function, for example to read a configuration file before the rest of the module runs. A CommonJS module cannot.

Variables that only CommonJS has. CommonJS gives each file the variables __dirname, which holds the path of the file's folder, and __filename, which holds the path of the file itself. ES modules do not have them. They use import.meta.dirname and import.meta.filename instead, which exist in Node.js 20.11 and later.

Using one from the other. An ES module can import a CommonJS module, and the value of module.exports arrives as the default import. Since Node.js 22.12, a CommonJS file can also require an ES module, as long as that module does not use await at its top level. Otherwise require fails with the error ERR_REQUIRE_ASYNC_MODULE.

Which One to Use

Use ESM for a new project if you can, because it is the language standard and the same syntax works in browsers. Expect to read CommonJS often, because many existing projects, tutorials and packages use it. Almost all examples in this course use CommonJS, so require and module.exports are what you will see in the lessons that follow.

Reading Progress

0%


Vote for new content

On This Page

Why Split Code into Modules

A CommonJS Module

The Same Module as an ECMAScript Module

Telling Node.js Which System a File Uses

How the Two Systems Differ

Which One to Use