
If you've been working with Node.js for any length of time, you've likely encountered the frustrating error message: "require of ES module is not supported." This common roadblock has plagued developers working with Node.js modules for years, creating unnecessary complexity when mixing module systems. However, Node.js 23 is changing the game by addressing this long-standing limitation.

Understanding the Module System Problem in Node.js
JavaScript has two separate module systems that have coexisted somewhat awkwardly: CommonJS (CJS) and ECMAScript Modules (ESM). This dual system has created an imbalanced situation where ESM can import CommonJS modules, but CommonJS cannot directly import ESM modules. For developers working with Node.js modules, this has been a significant pain point.
Prior to Node.js 23, when attempting to use require() to import an ESM module from a CommonJS file, you would encounter the infamous error: "require of ES module is not supported." This limitation forced developers into cumbersome workarounds that often involved restructuring their code.
The Traditional Workaround: Async Functions
The only legal way to import ESM modules in CommonJS code before Node.js 23 was to use an async function with dynamic imports. This looked something like:
// The only way to import ESM in CommonJS before Node.js 23
async function main() {
const { someFunction } = await import('./utils.mjs');
// Use the imported function
const result = someFunction();
console.log(result);
}
main().catch(console.error);
This workaround was problematic because it forced developers to restructure synchronous code into async functions. Since async/await is "viral" by nature (meaning once you use it, all calling code typically needs to be async as well), this could lead to major refactoring throughout an application.
How Node.js 23 Fixes the Module Problem
Node.js 23, currently an experimental version, introduces a game-changing improvement: CommonJS can now directly import ESM modules without complex workarounds. This creates a more balanced ecosystem where both module systems can freely interoperate.

With Node.js 23, you can now write code like this in a CommonJS file:
// In a CommonJS file (.cjs or .cts for TypeScript)
const { createTempDirectory } = require('./utils.mjs'); // or .mts for TypeScript
const tempDir = createTempDirectory();
console.log(tempDir);
This works seamlessly without any special configuration or code restructuring. The Node.js runtime now intelligently handles the module system differences behind the scenes.
One Important Limitation
There is one remaining limitation: if your ESM module uses top-level await, you still cannot import it directly with require(). This makes sense architecturally since CommonJS is fundamentally synchronous, while top-level await is asynchronous by nature.
// This will still cause an error in Node.js 23
// utils.mjs with top-level await
const result = await someAsyncOperation();
export function createTempDirectory() {
// Implementation using result
}
// index.cjs trying to import the above will fail
const { createTempDirectory } = require('./utils.mjs'); // Error: require cannot be used on an ESM graph with top level await
Using This Feature in Node.js 22
If you're not ready to switch to Node.js 23 but want to take advantage of this feature, you can use an experimental flag in Node.js 22. Simply add the `--experimental-require-module` flag when running your Node.js application:
node --experimental-require-module index.cjs
For TypeScript users, you might combine this with the TypeScript execution flag:
node --experimental-require-module --experimental-strip-types index.cts

Understanding Node.js Version Numbers
It's worth noting how Node.js version numbers work. Odd-numbered versions (like 23) are experimental releases where new features are introduced and tested. Even-numbered versions (like 22) are stable releases focused on reliability and bug fixes. This pattern allows Node.js to innovate while maintaining stability for production environments.
This is why the module import improvement appears first in Node.js 23 (experimental) and is available in Node.js 22 only behind an experimental flag. Once thoroughly tested, we can expect this feature to become standard in future stable releases.
Why This Matters for Node.js Developers
This change is significant for several reasons:
- It eliminates a major source of friction when working with modern JavaScript codebases
- It simplifies the migration path from CommonJS to ESM
- It reduces the need for complex async workarounds and code restructuring
- It allows developers to more easily integrate packages that use different module systems
- It brings Node.js more in line with other JavaScript runtimes like Bun that already support this feature
Practical Examples of Module System Usage
Let's look at some practical examples of how to work with different module types in Node.js. First, it's important to understand the file extensions and what they signify:
- .js/.cjs - JavaScript CommonJS modules (uses require/module.exports)
- .mjs - JavaScript ESM modules (uses import/export)
- .ts/.cts - TypeScript CommonJS modules
- .mts - TypeScript ESM modules
Here's how to import between different module types in Node.js 23:
// In a CommonJS file (.cjs)
const { feature } = require('./esm-module.mjs'); // Works in Node.js 23!
// In an ESM file (.mjs)
import { feature } from './commonjs-module.cjs'; // Has always worked
Conclusion: A Better Future for Node.js Modules
Node.js 23's improvement to the module system represents a significant step forward for JavaScript development. By eliminating one of the most frustrating errors developers encounter, Node.js is becoming more user-friendly and reducing unnecessary complexity.
This change aligns Node.js more closely with other modern JavaScript runtimes and creates a smoother path forward as the ecosystem continues to transition toward ESM. Whether you're maintaining legacy code or building new applications, this improvement will make working with Node.js modules significantly more pleasant.
As Node.js continues to evolve, we can expect more improvements that make developer experience smoother while maintaining the performance and reliability that has made Node.js one of the most popular JavaScript runtimes for both frontend and backend development.
Let's Watch!
Node.js 23 Eliminates JavaScript's Most Frustrating Module Error
Ready to enhance your neural network?
Access our quantum knowledge cores and upgrade your programming abilities.
Initialize Training Sequence