Mateen Kiani
Published on Thu Jul 10 2025·4 min read
Node.js has made server-side JavaScript a breeze, but the way we bring code into a file—modules—has evolved over time. While CommonJS and its require()
syntax served us well, ES Modules (import
/export
) are the modern standard. Yet many developers still mix both styles or stick to require()
out of habit. Ever wondered why mixing require()
and import
sometimes leads to odd errors or broken builds?
Switching fully to ES Modules and import
solves those headaches. Understanding how to enable ESM in Node.js, migrate existing code, and handle interoperability lets you write cleaner, future-proof code. Let’s dive into why import
matters and how it benefits your next project.
ES Modules (ESM) are the official JavaScript module standard. Browsers and modern build tools embrace them, giving you:
import
statements sit at the top and clearly show dependencies.Node.js supported CommonJS first, but recent releases back ESM natively. By declaring modules as ESM, you avoid runtime flags and Babel transpilation. You also tap into features like import.meta.url
and top-level await
. If you haven’t already, read about Node.js’s event loop and Node.js asynchronous nature to see how modules load in a non-blocking system.
Tip: Pick one module system per project. Mixing can lead to confusing loader errors.
To run ESM in Node.js:
In your package.json
, set:
{"type": "module"}
.mjs
or keep .js
if type
is set.node file.js
as usual.If you need compatibility, you can also:
--experimental-modules
flag on older Node versions..cjs
and .mjs
extensions to separate CommonJS and ESM files.For a quick start, see this Node.js project setup guide in VS Code.
When moving an existing codebase:
Update imports: ```js // Old CommonJS const fs = require('fs');
// New ESM import fs from 'fs'; ```
import pkg from 'pkg'
.export const x = 1;
then import { x } from './file.js'
.import()
..cjs
and load with require()
.Tip: Run your test suite after each batch of changes to catch missing exports early.
Sometimes you need both systems. Here’s a quick comparison:
Feature | CommonJS (require ) | ES Modules (import ) |
---|---|---|
Syntax location | Inline | Top of file |
Dynamic loading | require() works anywhere | Use import() function |
Named imports | Destructure after load | Native named imports |
File extension support | .js , .cjs | .mjs , .js with config |
Dynamic import()
lets you load modules at runtime:
async function loadConfig() {const config = await import('./config.js');return config.default;}
Quote: “Interop is possible, but clarity wins. Strive to convert entire modules when you can.”
Switching to import
rarely affects raw performance. But static import
lets build tools optimize your bundle:
Local tips:
js
import { readFile, writeFile } from 'fs/promises';
await
in ESM for sequential startup tasks:
js
const config = await import('./config.js');
Tip: Benchmark critical paths using simple timers before and after migration.
package.json
type
field consistent across services.no-commonjs
or plugins.ES Modules in Node.js are no longer experimental—they’re the future of modular JavaScript. Moving from require()
to import
helps you align with browser code, enable advanced build optimizations, and simplify dependency management. By updating your package.json
, renaming extensions, and migrating exports step by step, you can avoid downtime and buggy behavior. Remember to test thoroughly and keep an eye on interoperability when third-party packages haven’t caught up.
Takeaway: Commit to one module system per project, lean on static import
for clarity, and embrace the modern JavaScript ecosystem with confidence.