Skip to content

Node.js ES Modules — Complete Guide to import, export, and Modern JavaScript Modules

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Node.js ES Modules. We cover key concepts, practical examples, and best practices to help you master this topic.

ES modules (ESM) are the official JavaScript module standard using import and export statements, providing static analysis, tree-shaking, and top-level await in Node.js.

What You'll Learn

By the end of this tutorial, you'll use ES modules in Node.js: named and default exports, static and dynamic imports, package.json configuration, and migrating from CommonJS.

Why ES Modules Matter

ESM is the future of JavaScript modules. It enables better tooling through static analysis, smaller bundles via tree-shaking, and aligns browser and server module syntax. Modern frameworks like Express 5 support ESM natively.

Real-World Use

A new Node.js API project uses ESM to import Express, top-level await for database connections, and tree-shakable imports from utility libraries.

ESM Learning Path

flowchart LR
  A[CommonJS Modules] --> B[ES Modules]
  B --> C[Core Modules]
  C --> D[File System]
  D --> E[Express.js]
  A --> F{You Are Here}
  style F fill:#f90,color:#fff

Enabling ES Modules

Two ways to enable ESM in a Node.js project:

{
  "type": "module"
}

Or use the .mjs file extension for individual files. CommonJS files use .cjs.

Named Exports and Imports

Named exports allow multiple exports per module with specific names.

// math.js
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
export const PI = 3.14159;

// app.js
import { add, subtract, PI } from "./math.js";
console.log(add(5, 3));       // 8
console.log(PI);              // 3.14159

Renaming Imports

import { add as sum, subtract as diff } from "./math.js";
console.log(sum(5, 3));       // 8

Default Exports

Each module can have one default export.

// logger.js
export default function log(message) {
  console.log(`[LOG]: ${message}`);
}

// app.js
import log from "./logger.js";
log("Server started");        // [LOG]: Server started

Default exports can be imported with any name. This is convenient but reduces discoverability.

Combining Named and Default

// utils.js
export default function formatDate(date) { /* ... */ }
export const VERSION = "2.0";
export const AUTHOR = "DodaTech";

// app.js
import formatDate, { VERSION, AUTHOR } from "./utils.js";

Static vs Dynamic Imports

Static imports are evaluated at parse time. Dynamic imports load modules on demand.

// Static — must be at top level
import fs from "node:fs";

// Dynamic — can be conditional
async function loadPlugin(name) {
  const module = await import(`./plugins/${name}.js`);
  return module.default;
}

const plugin = await loadPlugin("auth");

Dynamic imports return a promise and work anywhere, including conditionals and functions.

Top-Level Await

ESM supports await at the top level without wrapping in an async function.

// db.js
import sqlite from "better-sqlite3";
const db = await sqlite(":memory:");
export default db;

Modules that depend on this will wait for it to resolve. This simplifies initialization code.

Package.json Exports

Modern packages define subpath exports for better Encapsulation.

{
  "exports": {
    ".": "./dist/index.js",
    "./utils": "./dist/utils.js",
    "./package.json": "./package.json"
  }
}

Users import specific paths: import { format } from "my-package/utils".

Common Mistakes

1. Forgetting File Extensions

ESM requires full file paths with extensions: import "./utils.js", not import "./utils".

2. Mixing require and import in ESM Modules

require is not available in ESM by default. Use createRequire from node:module if needed.

3. Importing JSON in ESM

JSON imports require assert syntax: import data from "./config.json" with { type: "json" }.

4. Circular Dependencies with ESM

ESM handles circular dependencies better than CommonJS but still causes issues. Restructure to avoid them.

5. Forgetting type:module in package.json

Without it, Node.js treats .js files as CommonJS. import statements throw SyntaxError.

Practice Questions

1. What is the difference between export default and export named?

export default exports a single value per module, imported without braces. Named exports export specific values, imported with matching names in braces.

2. Can you use require() in an ES module?

No, by default. Use import { createRequire } from "node:module" to create a require function if needed.

3. Why do ESM imports need file extensions?

ESM follows the ES spec, which requires complete specifiers. Node.js resolves extensions only in CommonJS.

4. What is tree-shaking and how do ES modules enable it?

Tree-shaking removes unused exports during Bundling. ESM's static structure lets tools know exactly what's used vs CommonJS's dynamic nature.

5. Challenge: Convert a CommonJS module to ESM that exports multiple utility functions and a default export.

// utils.mjs
export function formatDate(date) {
  return date.toISOString().split("T")[0];
}
export function parseCSV(text) {
  return text.split("\n").map(line => line.split(","));
}
export default { formatDate, parseCSV };

// app.mjs
import utils, { formatDate } from "./utils.mjs";
console.log(formatDate(new Date()));
console.log(utils.parseCSV("a,b\n1,2"));

FAQ

Can I use ES modules in older Node.js versions?

ESM is stable since Node.js 12. For older versions, use a transpiler like Babel or stick with CommonJS.

What is the difference between .mjs and .cjs?

.mjs forces ESM mode. .cjs forces CommonJS mode. .js follows package.json type field.

How do I import a CommonJS module from ESM?

CommonJS modules can be imported with default import: import mod from 'cjs-module'. Named exports may not work.

Can I use top-level await in CommonJS?

No. Top-level await is only available in ES modules.

Does Node.js support import maps?

Import maps are a browser feature. Node.js uses the exports field in package.json for similar functionality.

Mini Project: ESM Utility Library

Create a small library using ES modules with both named and default exports.

// string-utils.js
export function capitalize(str) {
  return str.charAt(0).toUpperCase() + str.slice(1);
}
export function truncate(str, maxLen) {
  return str.length > maxLen ? str.slice(0, maxLen) + "..." : str;
}
export default { capitalize, truncate };

// app.js
import strUtils, { capitalize } from "./string-utils.js";
console.log(capitalize("hello world"));           // Hello world
console.log(strUtils.truncate("Long text here", 5)); // Long ...

What's Next

Node.js Path Module Node.js File System Express.js

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro