Join our WhatsApp Channel
Cart / ₹0.00

No products in the cart.

Visit Support Portal
No Result
View All Result
Saturday, October 24, 2026
  • Home
  • Tutorials
    • Machine Learning
    • Python
    • Web Development
    • Javascript
    • Mathematics
    • Deep Learning
    • Artificial Intelligence
  • Store
  • Our ServicesBook Now
    • Managed Hosting Services
    • Web Development Services
    • Digital Marketing Services
    • Website Security Checker
  • Our Softwares
    • JaggoRe AIFree
    • Email ExtractorPaid
    • TimeWell AppFree
Get a Free Quote
  • Login
  • Register
Neuraldemy
Saturday, October 24, 2026
Cart / ₹0.00

No products in the cart.

No Result
View All Result
Neuraldemy
Get A Free Quote
Home NodeJS

How the Node.js Module System Works Step by Step for Beginners: CommonJS vs ES Modules

Neuraldemy by Neuraldemy
October 23, 2026
in NodeJS, Javascript, Web Development
Reading Time: 64 mins read
A A

In the previous tutorial on NodeJS, we explored how Node.js executes JavaScript using the V8 engine, libuv, and the event loop. Now, it is time to understand another essential part of building real Node.js applications: how to organize code across multiple files, and How Node.Js module system works.

Imagine you are building a small application that calculates prices, manages users, reads files, and communicates with a database. You could write everything inside one JavaScript file, but as the application grows, that file would become difficult to understand, test, and maintain.

The solution is to divide your code into smaller, reusable units called modules.

A module can contain functions, variables, classes, configuration, or other logic related to a particular task. One module might handle mathematical calculations, another might manage users, and a third might communicate with a database.

Node.js provides a built-in module system to make this possible. But there is one important detail: Node.js supports two main JavaScript module systems, CommonJS and ES Modules.

CommonJS uses require() and module.exports. ES Modules, often called ESM, use import and export.

Both systems help you reuse code, but they have different rules for loading modules, sharing values, resolving file paths, and handling dependencies.

In this tutorial, we will build both types of modules, examine what Node.js does behind the scenes, and learn when to use each system.

1. What Is a Module, and Why Does Node.js Need One?

A module is a separate unit of code that can expose selected values to other parts of your application.

Consider this function:

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

console.log(add(10, 5));

It works perfectly when everything lives in one file. However, what happens when several files need to use the same add() function?

You could copy and paste the function into every file. That creates duplication, and when you fix a bug, you must remember to update every copy.

A better approach is to write the function once, export it from one file, and import it wherever it is needed.

For example, your project might look like this:

my-app/
├── app.js
├── mathUtils.js
├── userService.js
└── package.json

Here, mathUtils.js contains mathematical functions, userService.js contains user-related logic, and app.js brings those pieces together.

Each file has its own module scope. This means that variables and functions you define inside a module are not automatically available to every other module.

For example:

// mathUtils.js

const secretNumber = 42;

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

module.exports = { add };

Another file can access add() through the exported object, but it cannot directly access secretNumber or the private implementation details of this module.

That boundary is useful. It lets you decide what other parts of your application are allowed to use.

One small clarification: browser JavaScript has also supported module-scoped code through native ES Modules for years. The shared-global-scope problem mainly applies to traditional browser scripts that are not loaded as modules. Node.js’s module systems provide their own mechanisms for keeping file-level code separate.

The core idea is simple: write code in one place, expose what other files need, and keep the rest private.

2. The Two Module Systems in Node.js

Before going deeper, let us understand the difference between CommonJS and ES Modules.

FeatureCommonJSES Modules (ESM)
Import syntaxrequire()import
Export syntaxmodule.exports or exportsexport
Standard or originOriginal Node.js module systemJavaScript language standard
Common file extensions.cjs, or .js in a CommonJS package.mjs, or .js in an ESM package
Relative import pathsNode can try supported extensions automaticallyInclude the file extension
__dirname and __filenameAvailable in normal CommonJS modulesNot provided as CommonJS variables
Top-level awaitNot supported as ordinary CommonJS syntaxSupported in ES Modules
Common useExisting applications and packagesModern JavaScript applications

Neither format is simply a different spelling for the other. They have different loading and export behavior.

CommonJS remains important because existing Node.js applications and packages use it extensively. ES Modules provide the standard import and export syntax that is also used by modern browser JavaScript.

We will start with CommonJS because it makes it especially easy to understand how Node.js creates a module and returns its exported values.

3. The Hidden Mechanism: Node.js’s CommonJS Module Wrapper

Have you ever wondered where require, module, and exports come from?

You might write this in a file:

const fs = require('node:fs');

console.log('Hello from Node.js');

Notice that you did not declare require yourself. You did not create a module object either, yet these identifiers are available in a normal CommonJS file.

The explanation is that Node.js wraps CommonJS module code inside a function before executing it.

Conceptually, the wrapper looks like this:

(function (exports, require, module, __filename, __dirname) {
  // Your CommonJS file executes inside this function.
});

This is a simplified representation of Node.js’s module wrapper, not code you need to write yourself.

Node.js provides five arguments to the wrapper:

exports is a reference that initially points to the module’s export object.

require is the function used to load other modules.

module represents the current CommonJS module.

__filename contains the absolute path to the current file.

__dirname contains the absolute path to the folder containing that file.

Why Does the Wrapper Matter?

First, it gives each CommonJS file a local scope.

Consider two files:

// fileA.cjs
const message = 'Hello from file A';
// fileB.cjs
const message = 'Hello from file B';

These declarations do not conflict just because they use the same variable name. Each file executes inside its own wrapper function.

Second, the wrapper provides the module-related values used by CommonJS.

Third, it makes it possible for Node.js to track a module and its dependencies as your application loads more files.

The wrapper is one reason CommonJS code can use require() and module.exports without explicitly importing those identifiers.

ES Modules work differently. They do not use the CommonJS wrapper, and they do not automatically provide require, module, exports, __filename, or __dirname.

4. CommonJS Explained: require() and module.exports

CommonJS is Node.js’s original module system. It has two important operations: exporting values and requiring those values from another file.

The usual pattern looks like this:

// Export a value
module.exports = {
  greeting: 'Hello!'
};

Another file can load it:

const message = require('./greeting.cjs');

console.log(message.greeting);

The require() call returns whatever the required module exposes through module.exports.

Think of module.exports as the public result a CommonJS file hands to other files.

Understanding module.exports

By default, a CommonJS module starts with an export object. You can attach properties to that object:

// greeting.cjs

function sayHello(name) {
  return `Hello, ${name}!`;
}

module.exports.sayHello = sayHello;

Another file can use the function:

// app.cjs

const greeting = require('./greeting.cjs');

console.log(greeting.sayHello('Alex'));

Output:

Hello, Alex!

Here, module.exports contains a property named sayHello. When another file calls require('./greeting.cjs'), Node.js returns that export object.

You can also export several functions at once:

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

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

module.exports = {
  add,
  multiply
};

The shorthand { add, multiply } is equivalent to { add: add, multiply: multiply }.

The consuming file can then call:

const math = require('./mathUtils.cjs');

console.log(math.add(5, 3));
console.log(math.multiply(5, 3));

Output:

8
15

You are not restricted to exporting objects. A CommonJS module can also export a function, class, or another value directly.

For example:

// calculate.cjs

function calculateTotal(price, taxRate) {
  return price + price * taxRate;
}

module.exports = calculateTotal;

The consuming file receives the function itself:

const calculateTotal = require('./calculate.cjs');

console.log(calculateTotal(100, 0.05));

Output:

105

Notice that we do not use calculateTotal.calculateTotal() here. We assigned the function itself to module.exports, so that is what require() returns.

5. module.exports vs exports: The Beginner Trap

This is one of the most common sources of confusion in CommonJS.

Inside a CommonJS module, Node.js initially makes exports point to the same object as module.exports.

Conceptually:

const module = {
  exports: {}
};

const exports = module.exports;

This is only a simplified illustration of the initial relationship. In actual Node.js code, module, exports, and require are provided in the module’s local scope.

Because exports initially refers to the same object, this works:

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

It is effectively the same as writing:

module.exports.add = function (a, b) {
  return a + b;
};

Both examples add a property to the same export object.

The Mistake: Reassigning exports

Now look at this example:

// wrong.cjs

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

This does not export the add function.

Why? Because the assignment changes what the local variable exports points to. It does not replace the module.exports property.

At the end of the module, Node.js returns module.exports, which still points to the original object.

If you need to replace the entire exported value, write:

// correct.cjs

module.exports = function add(a, b) {
  return a + b;
};

Now module.exports refers to the function, so require() returns that function.

A Simple Rule to Remember

Use exports.name = value when you want to add a property to the existing export object.

Use module.exports = value when you want to replace the complete exported value with a different object, function, class, or other value.

For beginners, module.exports is often the clearest choice because it makes the value being exported explicit.

6. Build Your First CommonJS Module Step by Step

Let us create a small math utility module and use it in an application.

To make the examples unambiguous regardless of package configuration, we will use the .cjs extension. Node.js treats .cjs files as CommonJS modules.

Create this folder structure:

node-modules-demo/
├── app.cjs
└── mathUtils.cjs

File 1: mathUtils.cjs

// mathUtils.cjs

// Private helper: not exported
function logOperation(name, a, b, result) {
  console.log(`[Log] ${name}(${a}, ${b}) = ${result}`);
}

function add(a, b) {
  const result = a + b;
  logOperation('add', a, b, result);
  return result;
}

function multiply(a, b) {
  const result = a * b;
  logOperation('multiply', a, b, result);
  return result;
}

// Export the public functions
module.exports = {
  add,
  multiply
};

The file contains three functions, but only two are exported.

logOperation() is a private helper. The other functions can use it because they are defined in the same module, but another module cannot access it through the exported object.

File 2: app.cjs

// app.cjs

const math = require('./mathUtils.cjs');

console.log('Testing CommonJS modules:');

const sum = math.add(10, 5);
console.log('10 + 5 =', sum);

const product = math.multiply(4, 6);
console.log('4 * 6 =', product);

console.log(
  'Private logOperation accessible?',
  typeof math.logOperation
);

Run the Application

Open a terminal inside the project folder and run:

node app.cjs

Expected output:

Testing CommonJS modules:
[Log] add(10, 5) = 15
10 + 5 = 15
[Log] multiply(4, 6) = 24
4 * 6 = 24
Private logOperation accessible? undefined

What Just Happened?

First, app.cjs calls require('./mathUtils.cjs').

Node.js locates the file, loads its code, and executes the module. The module assigns an object containing add and multiply to module.exports.

Node.js returns that object to app.cjs, and the variable math refers to it.

When the application calls math.add(10, 5), the function executes and uses the private helper. The fact that logOperation() is private does not prevent the other functions in the same module from using it.

Finally, typeof math.logOperation returns "undefined" because that function was not included in the exported object.

This is encapsulation in practice: the module exposes a small public interface while keeping its internal implementation private.

7. How Does require() Work Under the Hood?

We have now used require() several times. Let us look at the process behind the call.

Imagine this statement appears in app.cjs:

const math = require('./mathUtils.cjs');

At a high level, Node.js follows a sequence similar to this:

app.cjs
   |
   | require('./mathUtils.cjs')
   v
Resolve the module path
   |
   v
Check the module cache
   |
   +---- Already loaded? ---- Yes ----> Return cached exports
   |
   No
   |
   v
Load and execute the CommonJS module
   |
   v
Store its result in the module cache
   |
   v
Return module.exports
   |
   v
math receives the exported value

This diagram is a simplified mental model. The exact loading process includes additional details, but it captures the key steps a beginner needs to understand.

Step 1: Resolve the Module

Node.js determines what file or built-in module your request refers to.

For a relative path such as ./mathUtils.cjs, the location is calculated relative to the file making the require() call.

For a built-in module such as node:fs, Node.js recognizes that the request refers to a module provided by Node.js itself.

For a package name such as express, Node.js uses package-resolution rules to locate the dependency.

Step 2: Check the Module Cache

If the resolved CommonJS module has already been loaded, Node.js can return its cached export value rather than execute the same module code again.

If it has not been loaded yet, Node.js continues with the loading process.

Step 3: Load and Execute the Module

For a CommonJS JavaScript file, Node.js loads its source and executes it within the module wrapper.

Any top-level code in that module runs as part of this first evaluation.

For example:

// startup.cjs

console.log('startup.cjs is executing');

module.exports = {
  status: 'ready'
};

The message is printed when the module is first loaded and executed.

Step 4: Return the Exported Value

After the module has initialized, require() returns the value of module.exports.

That is why the following works:

const startup = require('./startup.cjs');

console.log(startup.status);

The variable startup receives the exported object.

One important detail: require() is synchronous. It returns the export value as part of the call, rather than returning a Promise for the module to finish loading. This is different from dynamic import(), which we will discuss later.

8. How Node.js Resolves Module Paths

Module resolution is the process of determining which module a particular import request refers to.

The string passed to require() gives Node.js clues about where to look.

There are four useful categories to understand.

A. Built-In Modules

Node.js includes modules for common operations such as working with files, paths, networking, and cryptography.

Examples include:

const fs = require('node:fs');
const path = require('node:path');
const http = require('node:http');

The node: prefix explicitly identifies a built-in Node.js module.

You will also see examples using require('fs') or require('path'). These are familiar forms for built-in modules, but using node: can make the intention clearer.

You do not need to create a file named fs.js or install these modules separately.

B. Relative Paths

A path beginning with ./ points relative to the current module’s directory.

const math = require('./mathUtils.cjs');

A path beginning with ../ points to a parent directory.

const config = require('../config.cjs');

The ./ and ../ prefixes matter because they tell Node.js that you are requesting a file path rather than a package by name.

C. Absolute Paths

You can also request a module using an absolute filesystem path:

const config = require('/app/config.cjs');

Absolute paths depend on the actual location of the file, so relative paths are often more convenient for application code that should work from different project directories.

D. Third-Party Packages

A package is typically installed in a node_modules directory.

For example, after installing a package such as Express, you might write:

const express = require('express');

Unlike ./mathUtils.cjs, the name express is a bare specifier. Node.js searches for the package according to its module-resolution rules, including applicable node_modules directories and the package’s entry-point configuration.

The package’s package.json can influence which file is exposed. Some packages also use an exports field to control which entry points other code is allowed to import.

Why require('mathUtils') May Fail

Suppose your project contains this file:

my-app/
├── app.cjs
└── mathUtils.cjs

This will not refer to the local file:

const math = require('mathUtils');

Node.js treats mathUtils as a package name rather than a relative file path.

Use this instead:

const math = require('./mathUtils.cjs');

Beginner rule: Use ./ for a nearby file, ../ for a file in a parent directory, and a package name when loading an installed dependency or supported package entry point.

9. Module Caching: Why Does Node.js Avoid Running the Same Module Repeatedly?

Imagine that three files all import the same configuration module.

const config = require('./config.cjs');

Does Node.js execute config.cjs every time another file calls require()?

Under normal circumstances, no. CommonJS modules are cached after their first load, based on the resolved filename.

Let us test this behavior.

File 1: config.cjs

console.log('Loading configuration...');

module.exports = {
  appName: 'My Node App',
  port: 3000
};

File 2: app.cjs

const configA = require('./config.cjs');
const configB = require('./config.cjs');

console.log(configA.appName);
console.log(configA === configB);

Run the application:

node app.cjs

Expected output:

Loading configuration...
My Node App
true

Notice that "Loading configuration..." appears only once even though the application calls require() twice.

Both variables also refer to the same exported object, so configA === configB returns true.

Why is this useful?

Module caching avoids repeating module initialization unnecessarily. It also means that if a module exports a shared mutable object, multiple consumers that load the same resolved module can observe the same object.

However, caching is based on the resolved filename, not simply the text inside the require() call. If two requests resolve to different physical module files, they can result in separate module instances. Modifying require.cache can also affect normal caching behavior.

For typical applications, the main lesson is that CommonJS modules are usually initialized once per resolved filename and reused afterward.

10. Meet ES Modules: The Modern import and export System

CommonJS came first in Node.js, but JavaScript later gained a standardized module system called ECMAScript Modules, or ES Modules (ESM).

ES Modules use syntax such as:

import { add } from './mathUtils.js';

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

The syntax is familiar to developers working with modern browser JavaScript, and it is supported in modern Node.js too.

The main difference is that ESM is its own module system, not simply another way to write CommonJS. It has different rules for module loading, imports, exports, and top-level execution.

How Do You Enable ES Modules in Node.js?

There are two clear ways to mark a file as an ES Module.

Option 1: Use the .mjs extension

A file named app.mjs is treated as an ES Module.

Option 2: Set "type": "module" in package.json

For a project that primarily uses ES Modules, create or update package.json:

{
  "name": "node-modules-demo",
  "version": "1.0.0",
  "type": "module"
}

Now ordinary .js files within this package scope are treated as ES Modules.

For a CommonJS package, setting "type": "commonjs" explicitly is also helpful.

Modern Node.js versions can sometimes detect ESM syntax in files whose format is not explicitly marked. However, relying on automatic detection can make projects harder to reason about and less predictable across environments. Explicitly declaring your module type is the clearer approach.

11. Named Exports vs Default Exports in ES Modules

ES Modules let you export specific values using named exports or a single default export.

Named Exports

A named export gives a value an exported name.

For example, create mathEsm.js:

// mathEsm.js

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

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

Another file can import those names:

import { add, multiply } from './mathEsm.js';

console.log(add(8, 12));
console.log(multiply(3, 4));

Output:

20
12

Notice two details. First, the imported names match the exported names. Second, the relative import includes .js.

That extension is important because Node.js requires fully specified relative paths for ESM imports. Unlike CommonJS require(), an ESM import should not rely on Node.js automatically trying common extensions or directory index files.

Default Exports

A module can also provide a default export.

// taxCalculator.js

export default function calculateTotalWithTax(
  amount,
  rate = 0.05
) {
  return amount + amount * rate;
}

Import the default export like this:

import calculateTotalWithTax from './taxCalculator.js';

console.log(calculateTotalWithTax(100));

Output:

105

The importing code chooses the local variable name for the default export, so it does not have to match the function’s original name.

Combining Named and Default Exports

You can use both styles in the same module:

// mathEsm.js

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

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

export default function calculateTotalWithTax(
  amount,
  rate = 0.05
) {
  return amount + amount * rate;
}

You can import all three values like this:

import calculateTotalWithTax, {
  add,
  multiply
} from './mathEsm.js';

console.log('Add:', add(8, 12));
console.log('Multiply:', multiply(3, 4));
console.log('Total with tax:', calculateTotalWithTax(100));

Output:

Add: 20
Multiply: 12
Total with tax: 105

A useful distinction is that named exports are imported by their exported names, while a default export is imported without specifying its exported name in braces.

12. Your First Complete ES Module Example

Let us build a working version of the math example using ES Modules.

Create this project structure:

esm-demo/
├── app.js
├── mathUtils.js
└── package.json

File 1: package.json

{
  "name": "esm-demo",
  "version": "1.0.0",
  "type": "module"
}

This tells Node.js to interpret the project’s .js files as ES Modules.

File 2: mathUtils.js

// Private helper function
function logOperation(name, result) {
  console.log(`[Log] ${name} result: ${result}`);
}

export function add(a, b) {
  const result = a + b;
  logOperation('add', result);
  return result;
}

export function multiply(a, b) {
  const result = a * b;
  logOperation('multiply', result);
  return result;
}

export default function square(number) {
  return number * number;
}

The file exports add, multiply, and square, but it does not export logOperation.

File 3: app.js

import square, {
  add,
  multiply
} from './mathUtils.js';

console.log('Testing ES Modules:');

console.log('Add:', add(10, 5));
console.log('Multiply:', multiply(4, 6));
console.log('Square:', square(5));

Run the application:

node app.js

Expected output:

Testing ES Modules:
[Log] add result: 15
Add: 15
[Log] multiply result: 24
Multiply: 24
Square: 25

The result resembles our CommonJS example, but the import and export syntax is different.

The key point is that the module system determines how Node.js interprets the file and connects its dependencies. CommonJS uses require() and module.exports, while ESM uses import and export.

13. Where Are __dirname and __filename in ES Modules?

If you have worked with CommonJS, you may have used these variables:

console.log(__filename);
console.log(__dirname);

They are useful when you need to locate a file relative to the current module.

However, these variables are not automatically available in ES Modules because ESM does not use the CommonJS wrapper.

A broadly compatible way to recreate them in Node.js ESM is to use import.meta.url:

import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';

const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

console.log(__filename);
console.log(__dirname);

Here is how it works:

import.meta.url provides the URL of the current module.

fileURLToPath() converts that file URL to a filesystem path.

dirname() extracts the containing directory from the filesystem path.

Recent Node.js versions also expose import.meta.filename and import.meta.dirname for file-based modules, but the fileURLToPath() approach remains useful when compatibility with older supported Node.js versions matters.

14. Can CommonJS and ES Modules Work Together?

Yes. Modern Node.js supports interoperability between CommonJS and ES Modules, so a project does not necessarily have to migrate every file at once.

However, the two systems do not behave identically, and the safest approach depends on which direction you are importing.

Importing CommonJS from ES Modules

Suppose a CommonJS module exports an object:

// mathCommon.cjs

module.exports = {
  add(a, b) {
    return a + b;
  }
};

An ES Module can import it using a default import:

// app.js, inside a package configured with "type": "module"

import math from './mathCommon.cjs';

console.log(math.add(10, 5));

The default import represents the CommonJS module’s module.exports value.

Node.js can also expose some CommonJS properties as named exports, but that behavior relies on static analysis and is not reliable for every export pattern. For beginners, using the default import is generally the safer option when importing a CommonJS module.

Importing ES Modules from CommonJS

One widely useful option is dynamic import():

// app.cjs

async function main() {
  const math = await import('./mathEsm.mjs');

  console.log(math.add(10, 5));
}

main().catch(console.error);

Dynamic import() returns a Promise that resolves to the ES Module’s module namespace object. That object contains the module’s exported values, including named exports.

This makes dynamic import() useful when CommonJS code needs to load an ES Module.

In recent Node.js releases, require() can also load certain ES Modules synchronously. However, that support has limitations: the ES Module must not use top-level await. The returned value is generally the module namespace object, so a default export may be accessed through its .default property. Applications that need broader compatibility can use dynamic import() from CommonJS instead.

Beginner recommendation: Avoid mixing module systems unnecessarily while you are learning. Pick one approach for a new project, understand it, and learn interoperability when a real dependency or migration requires it.

15. Common Beginner Mistakes and How to Fix Them

Let us review the mistakes that tend to cause the most confusion.

Mistake 1: Forgetting ./ for a Local CommonJS File

This can fail when mathUtils is just a file next to your application:

const math = require('mathUtils');

Node.js interprets the string as a package request.

Use the relative path instead:

const math = require('./mathUtils.cjs');

Mistake 2: Assigning a Value to exports Instead of module.exports

This does not replace the value that require() returns:

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

Use this:

module.exports = function add(a, b) {
  return a + b;
};

Remember that exports is initially a reference to the export object. Reassigning the local variable breaks that relationship.

Mistake 3: Omitting the Extension in an ESM Relative Import

This works in CommonJS:

const math = require('./mathUtils');

But this is not the normal ESM form:

import { add } from './mathUtils';

Use the fully specified relative path:

import { add } from './mathUtils.js';

Mistake 4: Assuming import Always Works in Any .js File

Node.js determines a file’s module format using markers such as .mjs, .cjs, and the nearest controlling package.json type field. Modern versions can also detect ESM syntax in certain otherwise ambiguous files.

That flexibility does not mean you should leave a project’s format unclear.

For a project using ES Modules, declare:

{
  "type": "module"
}

For a project using CommonJS, declare:

{
  "type": "commonjs"
}

Alternatively, use .mjs and .cjs extensions where appropriate.

Explicit settings help make your code easier to understand and reduce surprises when using other tools or moving between Node.js versions.

Mistake 5: Expecting require() to Return the Same Shape as import()

A CommonJS module typically returns whatever value is assigned to module.exports.

Dynamic import() returns a module namespace object.

For example, when dynamic import() loads an ESM module with a named export called add, you access it through the resulting namespace:

const math = await import('./mathEsm.mjs');

console.log(math.add(2, 3));

When a default export is involved, the namespace object normally exposes it through default:

const module = await import('./calculator.mjs');

console.log(module.default);

Understanding what each system returns can prevent confusing errors during interoperability.

Mistake 6: Exporting a Function but Trying to Use It as an Object

Suppose you write:

// calculate.cjs

module.exports = function calculate(a, b) {
  return a + b;
};

This is correct:

const calculate = require('./calculate.cjs');

console.log(calculate(2, 3));

But this does not work as intended:

const calculate = require('./calculate.cjs');

console.log(calculate.add(2, 3));

The exported value is a function, not an object with an add property.

Always check what the module exports before deciding how to use the returned value.

16. Which Should You Use: CommonJS or ES Modules?

For a new project, ES Modules are generally a good default choice because they use the standardized JavaScript import and export syntax supported by modern Node.js and browsers.

However, there is no need to treat CommonJS as obsolete. Many established Node.js projects and packages use it, and understanding require(), module.exports, and module caching will help you maintain existing applications.

Choose based on your project’s runtime requirements, dependencies, tooling, and consistency.

For example, if you are following a tutorial or contributing to a project written entirely in CommonJS, use the module system the project already expects. If you are starting a modern application from scratch and your dependencies support ESM, using "type": "module" is a sensible choice.

The most important skill is not memorizing which system is newer. It is understanding how each system resolves modules, loads code, and exposes values.

Official Documentation and Further Reading

For readers who want to go deeper, these official Node.js references explain the module system in greater detail:

  • Node.js: CommonJS Modules
  • Node.js: ECMAScript Modules
  • Node.js: Packages and the package.json type field

Support

Buy author a coffee

Support
SummarizeSendShareTweetScan
Previous Post

Observer Pattern in JavaScript Or TypeScript: Understanding Node.js EventEmitter

Neuraldemy

Neuraldemy

This is Neuraldemy support. Subscribe to our YouTube channel for more.

Related Posts

Observer Pattern in JavaScript Or TypeScript: Understanding Node.js EventEmitter

Deep Nodejs Architecture for Beginners: V8, Libuv, and the Event Loop Phases

Preventing Infinite Echo Loops in React Broadcast Channel

Handling Bfcache with Active WebSocket Connections in React: A Complete Tutorial

Preventing Out-of-Order Rollbacks in Optimistic UI: A Step-by-Step React Tutorial

100% Working Hostinger Discount Coupons & Promo Codes (2026)

Support

Buy author a coffee

Support

Support Neuraldemy

Newsletter

Top rated products

  • Probability and Statistics for Machine Learning and Data Science Probability and Statistics for Machine Learning and Data Science
    Rated 5.00 out of 5
    ₹599.00 Original price was: ₹599.00.₹349.00Current price is: ₹349.00.
  • SVM Notes: Optimization & Implementation SVM Notes: Optimization & Implementation
    Rated 5.00 out of 5
    ₹399.00 Original price was: ₹399.00.₹249.00Current price is: ₹249.00.
  • spidy mail hunter software Spidy Mail Hunter: Powerful And Intelligent Website & PDF Email Extractor Software (Windows App)
    Rated 4.78 out of 5
    ₹9,999.00 Original price was: ₹9,999.00.₹599.00Current price is: ₹599.00.
  • Linear Algebra For Machine Learning And Data Science Linear Algebra For Machine Learning And Data Science
    Rated 4.71 out of 5
    ₹699.00 Original price was: ₹699.00.₹449.00Current price is: ₹449.00.
  • Javascript DOM Notes Javascript Document Object Model Notes And Codes ₹499.00

Recent Posts

How the Node.js Module System Works Step by Step for Beginners: CommonJS vs ES Modules

Observer Pattern in JavaScript Or TypeScript: Understanding Node.js EventEmitter

Deep Nodejs Architecture for Beginners: V8, Libuv, and the Event Loop Phases

October 2026
M T W T F S S
 1234
567891011
12131415161718
19202122232425
262728293031  
« Aug    

Neuraldemy

Neuraldemy

Neuraldemy

Software Development

Neuraldemy helps you learn ML, AI, Web Dev and data science from scratch. Addtionally, we also provide web development and hosting services for businesses, and individuals

  • Get Started
  • Contact
  • Book Services
  • Privacy Policy
  • Terms Of Use
  • Support Portal
  • Managed Hosting Terms
  • Web Development Terms
  • Refund Policy
Neuraldemy

© 2026 - A learning platform by Odist Magazine

Welcome Back!

Login to your account below

Forgotten Password? Sign Up

Create New Account!

Fill the forms below to register

*By registering into our website, you agree to the Terms & Conditions and Privacy Policy.
All fields are required. Log In

Retrieve your password

Please enter your username or email address to reset your password.

Log In
No Result
View All Result
Join Our WhatsApp Channel
  • Home
  • Tutorials
    • Machine Learning
    • Python
    • Web Development
    • Javascript
    • Mathematics
    • Deep Learning
    • Artificial Intelligence
  • Store
  • Our Services
    • Managed Hosting Services
    • Web Development Services
    • Digital Marketing Services
    • Website Security Checker
  • Our Softwares
    • JaggoRe AI
    • Email Extractor
    • TimeWell App
  • Login
  • Sign Up
  • Cart
Order Details

© 2026 - A learning platform by Odist Magazine

This website uses cookies. By continuing to use this website you are giving consent to cookies being used.
0