Chapter 15: Node ES Modules

What are ES Modules (ESM) and why they matter in Node.js

ES Modules (also called ECMAScript Modules) are the official, modern way to write modular JavaScript — the same syntax that browsers have used for years (import / export).

In Node.js, we had CommonJS (require / module.exports) from the beginning (2009). But since Node.js 12+ (and especially Node.js 14+ with full stability), ES Modules became the recommended standard.

Why switch to ESM in 2025–2026?

  • Cleaner, more readable syntax (import vs require)
  • Top-level await is allowed (very useful!)
  • Better tree-shaking (modern bundlers remove unused code)
  • Most new libraries (Prisma, Zod, Drizzle, Hono, Fastify v5, etc.) are ESM-only
  • Same syntax as frontend (React, Next.js, Vite) → easier full-stack work
  • Future-proof — CommonJS is slowly being phased out

Quick summary table: CommonJS vs ESM

Feature CommonJS ES Modules (ESM)
Syntax require() / module.exports import / export
File extension Optional Usually required (.js)
Top-level await Not allowed Allowed
Dynamic import require() anywhere await import() (inside async)
package.json flag Not needed “type”: “module”
Modern library support 2026 Declining Dominant

Chapter 2 – How to enable ES Modules in your project

There are two main ways to tell Node.js “use ESM”:

Way 1 – Recommended: Add “type”: “module” in package.json (most common)

JSON

→ All .js files in your project are now treated as ES Modules.

Way 2 – Use .mjs extension (no package.json change needed)

Bash

→ Node.js automatically treats .mjs files as ES Modules.

Recommendation 2026: Use “type”: “module” + .js files → it’s cleaner, more consistent, and what almost everyone does now.

Chapter 3 – Basic ESM Syntax – Named Exports & Imports

3.1 Named exports (most common & recommended)

utils/date-utils.js

JavaScript

index.js (main file)

JavaScript

Run:

Bash

Output (example):

text

Important rule: In ESM you must write the file extension → ./utils/date-utils.js (not ./utils/date-utils)

Chapter 4 – Default Exports (when there is one main thing)

logger.js

JavaScript

Using it:

JavaScript

You can mix named and default:

JavaScript
JavaScript

Chapter 5 – Real-World Folder Structure with ESM

text

Example: user-service.js

JavaScript

Using in controller:

JavaScript

Chapter 6 – Top-Level Await (one of the best ESM features)

index.js (no need to wrap in async function!)

JavaScript

This is impossible in CommonJS — huge win for startup scripts!

Chapter 7 – Common Mistakes & How to Fix Them

Mistake Error Message / Problem Fix
Forget “type”: “module” SyntaxError: Cannot use import statement… Add “type”: “module” to package.json
Forget .js extension Cannot find module … Always write ./file.js
Using require() in ESM file ReferenceError: require is not defined Convert to import
Putting import inside if/for block SyntaxError: Import declarations … Move imports to top level
Circular dependencies Infinite loop or undefined values Refactor or use lazy loading
Mixing CommonJS & ESM in same project Hard-to-debug errors Choose one system per project

Chapter 8 – Summary & Recommendations (2026 style)

  • Use ES Modules for all new projects — “type”: “module” + .js files
  • Prefer named exports for most utilities & services
  • Use default export when a file has one clear main thing (class, service, router…)
  • Always write file extensions in imports
  • Enjoy top-level await for clean startup code
  • Most modern tools (Vite, Next.js, Prisma, Zod, Hono, Fastify) default to ESM

Homework / Practice Suggestions

  1. Create a small project with 6–8 modules (utils, services, controllers, routes)
  2. Write it first with CommonJS, then convert it fully to ESM
  3. Add top-level await to load config or connect to a database
  4. Try both named exports and default exports in the same file
  5. Intentionally create a circular dependency and see what happens (then fix it)

Would you like to go deeper into any of these topics next?

  • Step-by-step conversion from CommonJS to ESM
  • Barrel files (index.js re-exports)
  • Dynamic import() for lazy loading
  • Publishing dual CommonJS + ESM packages
  • Handling circular dependencies in real projects
  • Complete medium-sized project structure with ESM

Just tell me what you want to explore — I’ll continue with full, runnable examples. 😊

You may also like...

Leave a Reply

Your email address will not be published. Required fields are marked *