← Node & Express

Modules & npm

What does `require` do internally in CommonJS? What happens if you `require` the same file twice?

Show answer — try answering out loud first

require does four things: it resolves the module's path down to a concrete file, runs that file only once, wraps its code in a function (the module wrapper) that gives it variables like module, exports, require, __dirname, and __filename, and caches the result (module.exports). That's why modules are singletons: if you require the same file twice, Node does not run it again; it returns the exact same object stored in the cache.

When you write require('./mi-modulo'), Node does these steps in order:

  1. Resolves the path (resolution): turns what you passed it into the absolute path of a real file. If it's ./mi-modulo, it looks for mi-modulo.js, then .json, then .node, or a folder with index.js. If it's a bare name like express, it looks in node_modules.

  2. Checks the cache (require.cache): if that file was already loaded before, it returns the stored result and doesn't run anything else. This is where the singleton behavior comes from.

  3. Wraps the code (module wrapper): if it wasn't in the cache, Node wraps the entire content of the file inside a function, more or less like this:

    (function (exports, require, module, __filename, __dirname) {
      // ...aqui va el codigo de tu archivo...
    });
    

    That's why inside each module you have those five variables available without having declared them, and that's why the variables you declare in a module don't pollute the global scope: they live inside that function.

  4. Runs the function once and stores whatever ended up in module.exports in the cache. That object is what the code that did require receives.

The most important consequence: a module runs only once per process. If ten different files do require('./config'), the code of config.js runs once, and all ten receive the same shared object.

  • Resolves -> finds the exact file.
  • Caches -> if it was already loaded, it reuses it (singleton).
  • Wraps -> gives it module, exports, require, __dirname, __filename.
  • Runs once -> runs the file and stores module.exports.

A counter that lives inside a module and persists across requires, demonstrating that the module is a singleton.

// archivo: contador.js
let cuenta = 0; // this line ONLY runs the first time require is called

function incrementar() {
  cuenta += 1;
  return cuenta;
}

console.log("contador.js se esta EJECUTANDO"); // prints only once

module.exports = { incrementar };
// archivo: main.js
// Two requires of the SAME file:
const a = require("./contador");
const b = require("./contador");

// The code in contador.js ran only once,
// so "contador.js se esta EJECUTANDO" appears ONCE.

console.log(a === b); // true -> it's exactly the same object (singleton)

console.log(a.incrementar()); // 1
console.log(a.incrementar()); // 2
console.log(b.incrementar()); // 3  <-- uses the SAME state as 'a', doesn't reset

// Output:
// contador.js se esta EJECUTANDO
// true
// 1
// 2
// 3

Notice that b.incrementar() returns 3 and not 1: even though b came from a second require, it shares the same cuenta because Node handed back the cached object, not a fresh copy.

Believing that each require "resets" the module or creates a new instance. It doesn't: since the result is cached, everyone shares the same state. This is useful for things like a database connection or the configuration (you want a single one), but it's a trap if you store mutable state thinking each consumer has its own. Another related mistake: assigning properties to the exports variable and then reassigning module.exports to something else (module.exports = ...), which discards everything you put on exports, because in the end Node returns module.exports, not exports.

"When you call require, Node first resolves the path down to a concrete file, then checks the cache: if that file was already loaded, it returns the same object without running it again. If it wasn't cached, it wraps the file's code in a function, the module wrapper, which injects module, exports, require, __dirname, and __filename, runs it only once, and stores module.exports in the cache. That's why modules in CommonJS are singletons: two requires of the same file return exactly the same object and share state. That's great for configuration or a single connection, but it's a mistake to rely on each require giving you a fresh instance."

Quick challenge

What does this program print?

// logger.js
let llamadas = 0;
module.exports = () => {
  llamadas += 1;
  console.log("llamada numero", llamadas);
};

// app.js
const log1 = require("./logger");
const log2 = require("./logger");
log1();
log2();
log1();
See answer

It prints:

llamada numero 1
llamada numero 2
llamada numero 3

Even though log1 and log2 come from two different require calls, they point to the same cached function and share the module's llamadas variable. That's why the counter goes up 1, 2, 3 instead of resetting. log1 === log2 would be true.