Compare require and import in Node.js

Understand CommonJS and ES module loading, exports, file extensions, dynamic imports, and the limits of interoperability.

Best practices
Illustration accompanying a Node.js development guide

require() belongs to CommonJS; import and export belong to the ECMAScript module system. Both are supported by Node.js, but their loading and binding rules differ. Choose a module format deliberately rather than treating the keywords as interchangeable spellings.

CommonJS: values returned by require

// math.cjs
module.exports = { add: (a, b) => a + b };
// app.cjs
const { add } = require('./math.cjs');
console.log(add(2, 3));

require() is a function call that normally loads synchronously. It can appear inside a conditional or function. CommonJS modules are usually cached after loading, and module.exports supplies the value consumers receive.

ES modules: declarations and live bindings

// math.mjs
export const add = (a, b) => a + b;
export default function multiply(a, b) { return a * b; }
// app.mjs
import multiply, { add } from './math.mjs';
console.log(add(2, 3), multiply(2, 3));

Static imports must appear at the module's top level. Their dependencies can be analyzed before execution, and imported bindings stay connected to the exporting module's bindings. This is not a claim that all imported code executes at compile time. ES module evaluation can also involve top-level await.

Use .mjs for explicit ES modules and .cjs for explicit CommonJS. A package's "type": "module" makes its .js files ES modules. Relative ES module specifiers normally need their file extension; CommonJS's extension and directory lookup rules do not simply carry over.

Load a module dynamically

Dynamic import() returns a promise and works from either module system:

async function loadMath() {
  const { add } = await import('./math.mjs');
  return add(2, 3);
}
loadMath().then(console.log).catch(console.error);

Use it for conditional or deferred loading. The returned object is a module namespace, so a default export is available through its default property.

Interoperability and tooling

An ES module can import a CommonJS module, with its module.exports value available as the default export. Node may infer named exports from some CommonJS patterns, but that analysis is not a general guarantee; default-importing the exported object is more dependable when the package documents that interface.

Recent Node versions can also require() qualifying synchronous ES module graphs. A graph using top-level await cannot be loaded that way, and older runtimes have additional restrictions. Dynamic import() is the general asynchronous bridge; check the minimum runtime your package supports.

ES modules make static analysis easier for bundlers, which can support tree shaking. Node itself does not remove unused exports at runtime, and side effects or package structure can limit bundler optimizations. Browser support for ES modules does not make Node-specific APIs available in browsers.

CommonJS provides __filename and __dirname. For portable ES module code across older Node releases, derive a path from import.meta.url with fileURLToPath; newer runtimes also offer import.meta.filename and import.meta.dirname for applicable file modules.

See CommonJS modules and ES modules.

Get a tailored plan

Contact us

Join the community

Scan with WeChat
to join the community

Try Guance

Start online and pay only for what you use.

Get started

Choose a Guance plan

Code hosting