Node.js supports two module systems for organizing code. CommonJS is the original, using the require function to load modules synchronously and module.exports (or the shorter exports reference) to expose values. Each file is treated as its own module with its own scope, and modules are cached after the first load. This synchronous loading model is simple and works well on the server, where files are local and disk access is fast.
ES Modules (ESM) are the official JavaScript standard, supported in Node.js 12 and later. ESM uses the more familiar import and export syntax, is asynchronous, and supports top-level await. ESM can be enabled by adding "type": "module" to package.json or by giving files the .mjs extension. CommonJS, by contrast, is the default for .js files in projects that have not opted into ESM, and CommonJS modules can also be written explicitly using the .cjs extension.
The two systems differ in several important ways. CommonJS loads synchronously while ESM is asynchronous. CommonJS wraps each module in a function, whereas ESM runs in strict mode by default. Only ESM allows top-level await, which simplifies code that needs to wait for resources during initialization. Both systems cache modules, but ESM uses live bindings: if the exporting module updates a value, importing modules see the new value automatically. Choosing between them depends on the project, but modern Node.js code increasingly favors ESM for its alignment with the broader JavaScript ecosystem.