ES modules (ESM) are the official JavaScript module standard, the same import/export syntax used in modern browsers. Node.js has supported ES modules natively for years. This lesson covers how to enable and use them.
What Are ES Modules?
ES modules are the module system defined by the ECMAScript specification itself, not something Node.js invented. They use import and export statements, are statically analyzable (imports must be declared at the top level, not conditionally like require()), and support features CommonJS lacks natively, like top-level await.
To use ES modules in Node.js, either name your files with the .mjs extension, or set "type": "module" in the nearest package.json, which makes every .js file in that project tree an ES module by default.
// math.mjs
export function add(a, b) {
return a + b;
}
export default function multiply(a, b) {
return a * b;
}
// app.mjs
import multiply, { add } from './math.mjs';
console.log(add(2, 3)); // 5
console.log(multiply(2, 3)); // 6
A file can have one default export and any number of named exports at the same time, as shown here with multiply and add.
ES Module Syntax
export const value = 1; // named export
export default function() { } // default export
import value from './file.mjs'; // import default
import { value } from './file.mjs'; // import named
import * as ns from './file.mjs'; // import everything as a namespace
Named exports must be imported using their exact exported name (or renamed with as).
A module can have at most one export default.
Imports are hoisted and must appear at the top level, they cannot be conditional like require().
Top-level await is allowed directly inside ES modules, without wrapping in an async function.
ES Modules Cheatsheet
Core ESM syntax and configuration you'll use in modern Node.js projects.
Task
Syntax
Enable ESM project-wide
{ "type": "module" } in package.json
Enable ESM for one file
Use the .mjs extension
Named export
export const x = 1;
Default export
export default function() {}
Rename on import
import { x as y } from './f.mjs';
Current file URL
import.meta.url
Top-level await
const data = await fetchData();
Key Differences From CommonJS
Beyond syntax, ES modules behave differently at a fundamental level: they are asynchronous by specification (even though Node.js often resolves them quickly for local files), statically analyzable (enabling tooling like tree-shaking), and they run in strict mode automatically.
No __dirname/__filename, use import.meta.url with the url module instead.
No require() by default, though import.meta.resolve() and the module.createRequire() helper exist for interop.
Imports are read-only live bindings, not copies, reassigning an exported let in the source module updates it everywhere.
Getting __dirname in an ES Module
Since __dirname isn't defined in ES modules, you recreate it using import.meta.url, which gives you the current file's URL.
Recent Node.js versions also expose import.meta.dirname and import.meta.filename directly, removing the need for this workaround in newer projects.
Common Mistakes
Forgetting to add the .js/.mjs file extension in relative import paths, which is required by ES modules but optional in CommonJS.
Trying to use require() directly inside a "type": "module" project without creating one via createRequire().
Assuming __dirname exists in ES modules, it does not by default.
Mixing .mjs and "type": "commonjs" (or vice versa) and getting confused by which rules apply to which file.
Key Takeaways
ES modules are the standard JavaScript module system, enabled via .mjs files or "type": "module".
import/export statements are static and must appear at the top level of a module.
ES modules support top-level await and expose import.meta.url instead of __dirname.
Named exports are live, read-only bindings, not one-time copies of a value.
Pro Tip
New Node.js projects should default to ES modules ("type": "module") unless a specific dependency forces CommonJS, it matches the browser standard and avoids the growing list of CommonJS-only quirks.
You now know both Node.js module systems. Next, learn how npm helps you install, manage, and share packages built on top of these modules.