Node.js: Code Outside the Browser / Node.js Internals and How it Works

Requiring Modules in Practice

๐Ÿงช Part 1: Proving the Wrapper Function Exists

๐Ÿ“„Create a File modules.js

๐Ÿ”น Step 1: Log the arguments object

console.log(arguments);
  • arguments is a special array-like object available inside any regular function โ€” it contains all values passed into that function.

  • If logging arguments at the top level of a module shows real values, it proves the module's code is actually running inside a function.

โ–ถ๏ธ Run: node modules.js

Result: The logged arguments array contains 5 values โ€” matching the wrapper function's 5 parameters:

  1. ๐Ÿ“ค exports โ€” empty object (nothing exported yet)

  2. ๐Ÿ“ฅ require โ€” the require function

  3. ๐Ÿ“ฆ module โ€” contains module.exports

  4. ๐Ÿ“„ __filename โ€” full path to modules.js

  5. ๐Ÿ“ __dirname โ€” full path to the containing directory

โœ… This confirms: all module code really is wrapped inside a function behind the scenes.

๐Ÿ”น Step 2: View the Actual Wrapper Function Template

Node has an internal module literally called module that exposes the wrapper template:

console.log(require('module').wrapper);
  • This prints Node's actual wrapper function template โ€” showing exports, require, module, __filename, __dirname as parameters, with a body where your module's code gets inserted.

  • โœ… Confirms exactly what was explained conceptually in the previous lecture.


๐Ÿ“ค Part 2: Exporting with module.exports (Single Export)

๐Ÿ“„ Create a File test-module-1.js

๐Ÿ”น Step 1: Create a class

class Calculator {
  add(a, b) {
    return a + b;
  }
  multiply(a, b) {
    return a * b;
  }
  divide(a, b) {
    return a / b;
  }
}

๐Ÿ”น Step 2: Export it

module.exports = Calculator;

๐Ÿ“ฆ module.exports is used when exporting one single value โ€” here, the whole Calculator class.

๐Ÿ“„ File: modules.js โ€” Import and Use It

const C = require('./test-module-1');

const calc1 = new C();
console.log(calc1.add(2, 5));

Run: node modules.js and see the output

  • Since it's your own module (not core, not npm), use a relative path starting with ./.

  • .js extension can be dropped.

  • You can name the imported variable anything (C here) โ€” the name on the exporting side doesn't have to match.

  • โœ… Result: 7 (2 + 5) โœ”

โœจ Shorthand: Class Expression Directly in module.exports

Instead of declaring a named class then exporting it separately, assign the class expression directly:

module.exports = class {
  add(a, b) {
    return a + b;
  }
  multiply(a, b) {
    return a * b;
  }
  divide(a, b) {
    return a / b;
  }
};
  • โœ… Same result, fewer lines โ€” no need to name the class at all since you're exporting it immediately.


๐Ÿ“š Part 3: Exporting with exports Shorthand (Multiple Named Exports)

๐Ÿ“„ Create a File: test-module-2.js

exports.add = (a, b) => a + b;
exports.multiply = (a, b) => a * b;
exports.divide = (a, b) => a / b;

๐Ÿ“ค Instead of module.exports = ... (one value), add properties directly onto the exports object โ€” useful for exporting multiple named things (here, three separate functions).

๐Ÿ“„ File: modules.js โ€” Import and Use It

const calc2 = require('./test-module-2');

console.log(calc2.add(2, 5));       // 7
console.log(calc2.multiply(2, 5));  // 10

๐Ÿ“ฆ calc2 becomes an object containing all the properties added to exports (add, multiply, divide).

๐Ÿ”“ Using Destructuring on the Import

Since require() returns an object here, ES6 destructuring can pull out only the pieces you need:

const { add, multiply, divide } = require('./test-module-2');

console.log(add(2, 5));       // 7
console.log(multiply(2, 5));  // 10
  • Variable names inside { } must exactly match the property names on the exports object.

  • You can also import only what you need:

const { add, multiply } = require('./test-module-2');

โœ… This destructuring-on-import pattern is very common in Node and will be used throughout the course.


๐Ÿ’พ Part 4: Module Caching in Practice

๐Ÿ“„ Create a File test-module-3.js

console.log("Hello from the module");

module.exports = () => console.log("Log this beautiful text ๐Ÿ˜")
  • The console.log('Hello from the module') is top-level code โ€” it runs immediately when the module is first loaded.

  • module.exports here is a single function (not called yet, just defined).

๐Ÿ“„ File: modules.js โ€” Require and Call It 3 Times

require('./test-module-3')();
require('./test-module-3')();
require('./test-module-3')();
  • โšก Each line requires the module (which returns the exported function) and immediately calls it (()), all in one line โ€” equivalent to saving it to a variable first and calling it separately.

๐Ÿ–ฅ๏ธ Result when run:

Hello from the module
Log this beautiful text ๐Ÿ˜
Log this beautiful text ๐Ÿ˜
Log this beautiful text ๐Ÿ˜
  • 'Log this beautiful text' prints 3 times โ€” because the exported function itself was genuinely called 3 times.

  • 'Hello from the module' prints only once โ€” because of caching:

    • The module's top-level code only executes on the first require() call.

    • On the 2nd and 3rd require() calls, Node just returns the cached exports (the function) โ€” it does not re-run the module's code.


โœ… Key Takeaways

  • ๐ŸŽ Every module's code genuinely runs inside a hidden wrapper function โ€” provable by logging arguments or inspecting require('module').wrapper.

  • ๐Ÿ“ฆ module.exports = value โ†’ for exporting a single value (class, function, object).

  • ๐Ÿ“ค exports.name = value โ†’ for exporting multiple named values as properties of an object.

  • ๐Ÿ”“ Imports of multi-property exports can be destructured to pull out only what's needed.

  • ๐Ÿ’พ Modules are cached after their first load โ€” top-level code runs once only; later require() calls reuse the cached exports instead of re-executing the module.