0Pricing
Node.js Backend Development Bootcamp · Lektion

Von CommonJS zu nativen ES-Modulen migrieren

Überführen Sie require/module.exports in import/export und bewältigen Sie Fallstricke bei Dual Packages und Interoperabilität.

Von CommonJS zu nativen ES-Modulen migrieren ist eine kostenlose Node.js Backend Development Bootcamp-Lektion auf CoddyKit. Dies ist Lektion 1 von 4. Du kannst die komplette Lektion unten kostenlos lesen – dann übst du sie direkt im Browser mit einem integrierten Code-Editor und einem KI-Tutor rund um die Uhr. Sie ist Teil des Node.js Backend Development Bootcamp-Lernpfads, und dein Fortschritt wird über Web und CoddyKit-App synchronisiert. Der Node.js Backend Development Bootcamp-Kurs umfasst insgesamt 4 Lektionen.

Teile dieser Lektion wurden noch nicht übersetzt und werden auf Englisch angezeigt.

Two Module Systems, One Runtime

Node.js historically used CommonJS (CJS): you load code with require() and expose it with module.exports. Modern JavaScript has a built-in standard, ES Modules (ESM), using import and export.

  • CommonJS loads modules synchronously at runtime.
  • ESM is statically analyzed, loaded asynchronously, and is the official ECMAScript standard.

Node now supports both, but mixing them has rules. This lesson walks you through migrating a backend project from CJS to native ESM cleanly.

// CommonJS (old)
const fs = require('fs');
module.exports = { readConfig };

// ES Modules (new)
import fs from 'fs';
export { readConfig };

Telling Node You Mean ESM

Node decides how to treat a file based on its extension and the nearest package.json:

  • "type": "module" in package.json → .js files are treated as ESM.
  • No type field (or "commonjs") → .js files are CommonJS.
  • .mjs is always ESM; .cjs is always CommonJS, regardless of type.

The cleanest migration step is adding "type": "module" once, then fixing the files it breaks.

{
  "name": "my-api",
  "version": "1.0.0",
  "type": "module",
  "main": "src/server.js",
  "scripts": {
    "start": "node src/server.js"
  }
}

Converting Exports

Replace module.exports and exports.foo with export statements.

  • module.exports = X (single value) → export default X.
  • exports.foo = ... (multiple named) → export const foo = ... or a grouped export { foo, bar }.

Prefer named exports for utilities so consumers get autocompletion and clearer imports.

// Before (CJS)
// module.exports.add = (a, b) => a + b;
// module.exports.PI = 3.14159;

// After (ESM)
export const add = (a, b) => a + b;
export const PI = 3.14159;

console.log(add(2, 3)); // 5
console.log(PI);        // 3.14159

Converting Imports

Replace require() with import. Match the export style:

  • Named: const { add } = require('./math') → import { add } from './math.js'.
  • Default: const express = require('express') → import express from 'express'.

Critical rule: in ESM, relative imports of your own files must include the file extension (.js). Node will not guess it for you like CommonJS did.

// Before (CJS)
// const { add } = require('./math');

// After (ESM) — note the explicit .js extension
import { add } from './math.js';

console.log(add(10, 5)); // 15

No More __dirname or __filename

ESM does not provide the CommonJS globals __dirname and __filename. If your backend builds file paths (config, uploads, templates), you must recreate them from import.meta.url.

Modern Node (20.11+) also exposes import.meta.dirname and import.meta.filename directly, which is the simplest option going forward.

import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

// Classic portable approach
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);

const configPath = join(__dirname, 'config.json');
console.log(configPath);

// Node 20.11+ shortcut:
// const dir = import.meta.dirname;

Importing JSON and Built-ins

Two more changes that bite backend projects:

  • JSON: require('./data.json') no longer works directly. Use an import attribute: import data from './data.json' with { type: 'json' }.
  • Core modules: prefer the node: prefix (e.g. import { readFile } from 'node:fs/promises'). It is unambiguous and future-proof.

If you target older Node, reading JSON via fs avoids attribute-syntax compatibility concerns entirely.

import { readFile } from 'node:fs/promises';

// Robust, version-agnostic way to load JSON in ESM
const raw = await readFile(new URL('./pkg.json', import.meta.url));
const pkg = JSON.parse(raw);
console.log(pkg.name);

Top-Level await Is a Superpower

One genuine upgrade ESM gives backend code: top-level await. In CommonJS you had to wrap async startup in an IIFE. In an ESM module you can await directly at the top level.

This makes database connections, config fetching, and warm-up logic much cleaner in server entry files.

// ESM entry file
async function connectDb() {
  await new Promise((r) => setTimeout(r, 50));
  return { status: 'connected' };
}

const db = await connectDb(); // top-level await — no IIFE needed
console.log('DB:', db.status);

Interop: ESM Importing CommonJS

You will still depend on CJS-only npm packages. Good news: ESM can import CommonJS. Node wraps the package's module.exports as the default export.

  • Use import pkg from 'cjs-lib' to get module.exports.
  • Node tries to detect named exports too, but for complex CJS this can fail — destructure from the default instead.
// 'lodash' is CommonJS; the whole export object is the default
import _ from 'lodash';

const { chunk } = _; // safe: destructure from default
console.log(chunk([1, 2, 3, 4], 2)); // [[1,2],[3,4]]

Interop: CommonJS Loading ESM

The reverse is harder. A CommonJS file cannot require() an ESM module synchronously (older Node throws ERR_REQUIRE_ESM). Options:

  • Use a dynamic import(), which returns a Promise — works from inside async functions in CJS.
  • Node 22+ added experimental synchronous require() of ESM, but don't rely on it for portable code.

This asymmetry is the main reason teams migrate the whole codebase to ESM rather than mixing.

// Inside a CommonJS file:
async function run() {
  // dynamic import works even from CJS
  const { add } = await import('./math.mjs');
  console.log(add(4, 6)); // 10
}

run();

Dual-Package Publishing

If you publish a library, some users are ESM and some are CJS. The modern solution is the exports field with conditional exports, shipping both builds.

  • import condition → the ESM entry.
  • require condition → the CJS entry.

Beware the dual-package hazard: if both builds get loaded, you get two copies of your module state (e.g. two separate singletons). Keep stateful logic in a single internal module both builds import.

{
  "name": "my-lib",
  "type": "module",
  "main": "./dist/index.cjs",
  "module": "./dist/index.js",
  "exports": {
    ".": {
      "import": "./dist/index.js",
      "require": "./dist/index.cjs",
      "types": "./dist/index.d.ts"
    }
  }
}

A Pragmatic Migration Checklist

A reliable order of operations for an existing backend:

  • Add "type": "module" to package.json.
  • Rename any file that must stay CJS to .cjs.
  • Convert require/module.exports to import/export.
  • Add .js extensions to all relative imports.
  • Replace __dirname/__filename with import.meta helpers.
  • Fix JSON imports and CJS-default interop.
  • Run the test suite; let failures point to remaining require calls.

Tools like cjstoesm or codemods can automate the bulk edits, but always review the diff.

Quick Check

You convert a backend file to ESM and add "type": "module". Suddenly an import of your own helper throws ERR_MODULE_NOT_FOUND. What is the most likely fix?

Recap

You migrated a Node.js backend from CommonJS to native ES Modules. Key takeaways:

  • Opt in with "type": "module"; use .cjs/.mjs to override per file.
  • Swap require/module.exports for import/export, and always include the .js extension on relative imports.
  • Recreate __dirname via import.meta.url (or use import.meta.dirname).
  • ESM can import CJS (as default export); CJS must use dynamic import() for ESM.
  • Top-level await simplifies async startup.
  • For libraries, use conditional exports and beware the dual-package hazard.

With these rules, you can confidently modernize any Node service to standard ES Modules.

Häufig gestellte Fragen

Ist die Lektion „Von CommonJS zu nativen ES-Modulen migrieren“ kostenlos?

Ja — der vollständige Text von „Von CommonJS zu nativen ES-Modulen migrieren“ ist hier im Web kostenlos zu lesen. Um sie interaktiv zu üben (integrierter Code-Editor und 24/7 KI-Tutor) und den Rest des Node.js Backend Development Bootcamp-Kurses freizuschalten, upgrade auf CoddyKit PRO. Der Node.js Backend Development Bootcamp-Kurs umfasst insgesamt 4 Lektionen.

Was lerne ich in „Von CommonJS zu nativen ES-Modulen migrieren“?

Überführen Sie require/module.exports in import/export und bewältigen Sie Fallstricke bei Dual Packages und Interoperabilität. Du übst Node.js Backend Development Bootcamp mit praktischem Code, den du direkt im Browser ausführst, und ein 24/7 KI-Tutor beantwortet deine Fragen während du die Lektion bearbeitest.

Brauche ich Erfahrung, um Node.js Backend Development Bootcamp zu starten?

Keine Vorkenntnisse erforderlich. Node.js Backend Development Bootcamp auf CoddyKit ist für Anfänger bis fortgeschrittene Lernende strukturiert, sodass du hier starten oder von Anfang an beginnen und in deinem eigenen Tempo voranschreiten kannst. Dies ist Lektion 1 von 4.

Wie lange dauert die Lektion „Von CommonJS zu nativen ES-Modulen migrieren“?

Die meisten CoddyKit-Lektionen dauern etwa 5–10 Minuten. Jede ist kompakt und interaktiv, sodass du stetig Fortschritte machst und genau dort weitermachst, wo du aufgehört hast – im Web und in der App.

Kann ich in dieser Node.js Backend Development Bootcamp-Lektion Code schreiben und ausführen?

Ja. Jede Node.js Backend Development Bootcamp-Lektion enthält einen integrierten Code-Editor, sodass du echten Code direkt in deinem Browser schreibst und ausführst und sofort KI-Feedback erhältst — ohne lokale Einrichtung erforderlich.

Alle Lektionen in diesem Kurs

  1. Von CommonJS zu nativen ES-Modulen migrieren
  2. tsconfig für Node-Backend-Projekte konfigurieren
  3. Typsichere Umgebungskonfiguration und Laufzeitvalidierung
  4. Schnelle Iteration mit tsx, Hot Reload und Source Maps
← Zurück zu Node.js Backend Development Bootcamp