BugForge
All lessons
Node.js · 05/19 6 min

Modules: require, import and the cache

module.exports vs exports, the require cache, circular imports and ESM differences.

Half of "it's undefined" bugs in a Node codebase are really module bugs: something was exported under a different name, or not exported at all.

CommonJS in one picture

// math.js
function add(a, b) { return a + b; }
module.exports = { add };          // the object require() returns

// app.js
const { add } = require("./math"); // destructure the named export
const math = require("./math");    // or keep the whole object: math.add

Bugs that show up in labs

Bug Why it breaks Fix
exports = { add } reassigns the local variable; module.exports is still {} module.exports = { add } or exports.add = add
const add = require("./math") you got the object, not the function: add is not a function destructure: const { add } = …
module.exports = router then require("./routes").router the export is the router; it has no .router match the shape on both sides
two files require each other the second sees a half-finished module.exports move shared code to a third module, or require lazily inside the function
typo in path casing (./Models/user) works on macOS, fails on Linux CI match the real casing

The require cache

A module's top-level code runs once. Every later require returns the same object, so a module is a singleton:

// db.js
const users = [];                  // shared by every file that requires db.js
module.exports = { users };

That's handy for an in-memory store, and a trap for tests: state leaks between tests unless something resets it (beforeEach(() => users.length = 0)).

ES modules

import { add } from "./math.js";   // extension required in Node ESM
export function add(a, b) { … }
export default router;             // import router from "./routes.js"
  • __dirname and require don't exist in ESM. Use import.meta.url (or import.meta.dirname in recent Node).
  • Imports are hoisted and live: you can't conditionally import at the top level; use await import(...).
  • Mixing default and named exports is the ESM version of the exports bug: import router from vs import { router } from must match what the file exports.
Practise it now
Next in Node.js
Errors in Node and Express