Only show guides for your app
Two module systems, one Node runner. What "type":"module" actually flips, which errors still bite on Falix's Node, and the exact fix for each — verified in the real container.
Node has two ways to split code into files: CommonJS (require / module.exports), the original, and ES Modules (import / export), the newer standard. Most "why won't my code run" confusion on the Node.js application comes from mixing the two by accident. This guide shows how Node decides which mode a file is in, exactly what breaks when you get it wrong, and the one-line fix for each — all on the same runner Falix uses.
| At a glance | |
|---|---|
| You need | A Falix server running the Node.js application |
| Plan | Any — the module system is the same on free and premium |
| Time | Fifteen minutes |
| Good to already know | Node.js on Falix (entry file, auto npm install) |
| CommonJS (CJS) | ES Modules (ESM) | |
|---|---|---|
| Import | const x = require('x') |
import x from 'x' |
| Export | module.exports = ... |
export / export default |
| Folder path | __dirname, __filename |
import.meta.dirname, import.meta.url |
| Top-level await | No | Yes |
| Loads the other kind | import() (dynamic) or require() an ESM |
import works for both |
Neither is "better" for a Falix server — both run fine. What matters is that every file is in exactly one mode, and that you don't reach for the wrong keyword inside it.
The Node.js application starts your code with node <Main file> (the Main file variable, default index.js). Before running a line, Node picks the file's mode by these rules, in order:
.mjs file is always ESM. A .cjs file is always CommonJS. Nothing overrides these..js file, the nearest package.json decides. If it contains "type": "module", every .js file in that folder tree is ESM. If it says "type": "commonjs" or has no type field, .js is CommonJS..js file with no type set that clearly uses import/export is detected and run as ESM automatically — so a lone import at the top of index.js may just work with no config at all.🎯 Good to know: That last rule is why old tutorials warning "you must set
type:modulebefore you can useimport" are now half-true. Detection is convenient, but it's fragile — the moment a file mixes paradigms, or you pick an older Node version in Settings, the strict errors below come back. Choosing one system on purpose beats relying on the sniffer.
These are the errors you'll really see in the Console, each with why it happens and the one change that fixes it:
| Console error | What you did | Fix |
|---|---|---|
ReferenceError: require is not defined in ES module scope |
Called require() inside an ESM file ("type":"module" or .mjs) |
Switch that line to import, or rename the file to .cjs |
ReferenceError: __dirname is not defined in ES module scope |
Used __dirname/__filename in ESM |
Use import.meta.dirname / import.meta.filename |
ReferenceError: module is not defined in ES module scope |
Used module.exports in ESM |
Use export / export default instead |
SyntaxError: Cannot use import statement outside a module |
Used import in a file Node treats as CommonJS — a .cjs file, or any .js on an older Node version |
Rename to .mjs, set "type":"module", or use require() |
Error [ERR_REQUIRE_ASYNC_MODULE] |
require()d an ESM file that uses top-level await |
Load it with dynamic import() and await the result |
Notice what is not on that list: on Falix's current Node runner, require()-ing an ordinary ESM module works — the old ERR_REQUIRE_ESM crash is largely gone. The one case that still stops require() is an ESM module that uses top-level await; there you must switch to dynamic import().
"type": "module" flip, concretelyAdding one line to package.json re-labels every .js file in the project as ESM:
{
"type": "module"
}
After that flip, three things you may have written as CommonJS stop existing and need their ESM equivalents:
// Before (CommonJS) — now throws "require/module/__dirname is not defined"
const fs = require('node:fs');
module.exports = { start };
const here = __dirname;
// After (ESM) — the equivalents
import fs from 'node:fs';
export { start };
const here = import.meta.dirname; // the folder this file lives in
If you'd rather keep one stubborn file as CommonJS inside an otherwise-ESM project, rename just that file to .cjs — the extension overrides the type field. The reverse works too: a single .mjs file stays ESM even when package.json has no type.
You'll sometimes need a library that only ships as ESM from CommonJS code (or vice-versa). The bridge is dynamic import(), which returns a promise and works from both systems:
// CommonJS file loading an ESM-only dependency
async function main() {
const { default: fetch } = await import('node-fetch');
const res = await fetch('https://example.com');
console.log(res.status);
}
main();
This is also the fix for ERR_REQUIRE_ASYNC_MODULE: whenever require() refuses because the target uses top-level await, await import(...) instead.
⚠️ Heads up: Don't scatter both
requireandimportthrough one file hoping Node sorts it out. Pick the file's system, keep every static import in that style, and reach for dynamicimport()only as the deliberate bridge between the two.
require); many newer libraries and guides are ESM (import)."type": "module" once and use import throughout.await import(...) from your existing CommonJS code..ts file do nothing / won't run — that's a different topic: a bare .ts Main file doesn't run on the Node app. See TypeScript projects on Falix.Cannot find module after switching styles — an unrelated missing dependency; read the npm output at the top of the console and install it from the Packages page.Everything past this is standard Node behavior — the official reference at nodejs.org covers the full module-resolution rules if you want the deep version.