Backend
Node.js
Understanding IIFE in Node.js

Understanding IIFE, Module Privacy, and require in Node.js

Overview

Immediately Invoked Function Expressions (IIFE) in Node.js, how to keep module variables and functions private, and details the mechanism of the require statement in Node.js.


Immediately Invoked Function Expression (IIFE)

An IIFE is a JavaScript design pattern that runs a function immediately after it's defined. It helps create a private scope and avoids polluting the global namespace.

Syntax

(function() {
    // Code inside here is private
})();

With Parameters

(function(param) {
    console.log(param);
})(5); // Logs 5

Use in Node.js

IIFEs are useful in Node.js for:

  1. Encapsulation: Keep variables and logic private within a module.
  2. Avoiding Globals: Prevent variable leakage to the global scope.
  3. Self-contained Blocks: Useful within larger modules or libraries.

Example

// myModule.js
const myModule = (function() {
    const privateVariable = 'I am private';
 
    function privateFunction() {
        console.log('This is a private function');
    }
 
    return {
        publicFunction: function() {
            console.log('This is a public function');
            privateFunction();
        }
    };
})();
 
module.exports = myModule;

Module Privacy in Node.js

Each module in Node.js has its own scope. Variables/functions inside a module are private unless explicitly exported.

Example

// myModule.js
const privateVariable = 'I am private';
 
function privateFunction() {
    console.log('This is a private function');
}
 
module.exports = {
    publicFunction: function() {
        console.log('This is a public function');
    }
};

require Mechanism in Node.js

The 5 Steps of require()

  1. Resolving – Determines full file path
  2. Loading – Reads file contents
  3. Wrapping – Wraps code in function:
(function (exports, require, module, __filename, __dirname) {
    // Module code
});
  1. Evaluation – Executes the wrapped code
  2. Caching – Stores the result for future calls

Example

// index.js
const myModule = require('./myModule');
myModule.publicFunction();

Module Wrapping in Node.js

All code in Node.js modules is wrapped in an IIFE. This provides local scope.

What You Write:

const x = 100;
module.exports = () => 'Hello';

What Node.js Actually Executes:

(function(exports, require, module, __filename, __dirname) {
  const x = 100;
  module.exports = () => 'Hello';
})(module.exports, require, module, __filename, __dirname);

Module Caching

Node.js caches modules after first require. Future requires return cached version.

const mod1 = require('./module.js');
const mod2 = require('./module.js');
// mod1 === mod2 (same object from cache)

Exploring Node.js Source Code

  • CommonJS Loader: lib/internal/modules/cjs/loader.js
  • ESM Loader: lib/internal/modules/esm/loader.js
  • Helpers: lib/internal/modules/helpers.js

Key Internal Functions

  • Module._load() – Main loading logic
  • Module._compile() – Compiles/wraps module code
  • Module.wrap() – Creates the IIFE wrapper

Practical Examples

Accessing Module Variables

// module.js
const privateVar = 'secret';
module.exports = { publicVar: 'hello' };
 
// app.js
const mod = require('./module.js');
console.log(mod.publicVar); // 'hello'
console.log(mod.privateVar); // undefined

Using Module Parameters

// module.js
console.log(__filename); // Full file path
console.log(__dirname); // Directory path

Behind the Scenes Example

index.js

const num = 10;
let msg = 'I am Index.js';
module.exports = { num, msg };

app.js (imagined IIFE)

(function(exports, require, module, __filename, __dirname){
  console.log('App file loading');
  const { num, msg } = require('./index.js');
  console.log('num:', num, 'msg:', msg);
  console.log('app end');
})(...);

Types of require() calls

  • Internal file: require('./filename.js')
  • JSON: require('./data.json')
  • Text/log file: require('./logs.txt')
  • Core module: require('http')
  • External npm module: require('express')

How module.exports Exposes Private Modules

When you use module.exports, you decide what gets exposed to the outside world. Even private variables or functions can be made public if you include them in the exported object.

Example: Private to Public Exposure

// user.js
const secret = 'top-secret';
const getSecret = () => secret;
 
module.exports = {
  reveal: getSecret // Exposing private function
};
// app.js
const user = require('./user');
console.log(user.reveal()); // Outputs: 'top-secret'

Here, secret stays private, but getSecret() is shared via module.exports.


Complete require() Flow with Types

When you call require(), Node.js performs the following steps:

  1. Resolving: Determine the absolute file path.

    • Local file: require('./file.js')
    • JSON file: require('./data.json')
    • Text/Log file: require('./file.txt')
    • Core module: require('fs'), require('http')
    • Installed npm module: require('express')
  2. Loading: Node reads the file contents.

  3. Wrapping: Code is wrapped in:

    (function(exports, require, module, __filename, __dirname) { ... })
  4. Evaluation: Executes the code.

  5. Caching: Saves the output for reuse.


If you found this helpful, please share this with your besti!