Node CJS/ESM Default Import "is not a function" in CI - Fix the Interop
When ESM imports a CommonJS module, Node wraps module.exports as the default export. Calling the namespace, or the wrong default level, throws "is not a function".
What this error means
An ESM file imports a CommonJS package and calling the default import throws TypeError: <name> is not a function, because the callable lives one level deeper.
import express from 'express';
const app = express();
^
TypeError: express is not a function
at file:///home/runner/work/app/app/src/server.mjs:2:15Common causes
Interop default wrapping a CommonJS export
Node exposes module.exports as default, so the real function may be express.default rather than express when interop differs.
A bundler default-interop setting mismatch
esModuleInterop or a bundler setting changes whether the default is the function or a wrapper object.
How to fix it
Resolve the callable from the namespace
- Import the namespace and read .default if the namespace itself is not callable.
- Use the resolved value as the function.
import pkg from 'legacy-cjs';
const create = pkg.default ?? pkg;Enable esModuleInterop when compiling
- Set esModuleInterop in tsconfig so default imports of CommonJS resolve to the callable.
- Rebuild and re-run.
// tsconfig.json
"esModuleInterop": trueHow to prevent it
- Keep esModuleInterop consistent across build and runtime, prefer named imports where a package exposes them, and verify the interop shape when mixing CommonJS and ESM.