Modules in Node.js


When programming in Node.js, it's important to understand the basics of the module system, such as scope, exports, and how that allows you to do some caching.

Scope

Every file (module!) in Node.js is wrapped in the following function [1]:

(function (exports, require, module, __filename, __dirname) {
    // Module code actually lives in here
});

Therefore, variables are not by default put in the global scope. Things are only exposed when you edit module.exports.

exports vs. module.exports

In JS, objects and arrays are assigned by reference. module.exports is simply an object that the require function returns when called with module’s name as its input.

exports, on the other hand, is a reference to module.exports, nothing more. Here’s how to not abuse it:

// Don't do this!

// in ./a.js
exports = { name: 'Alex' }

// in index.js
const a = require('./a') // {}

Since exports is a reference, you can’t overwrite it. module.exports will not get updated.

// Both of these are fine

// in a.js
module.exports = { name: 'Alex' }

// in a.js
module.exports.name = 'Alex'

Caching

The first time modules are read, they are cached in `require.cache`. This is a classic example of memoization.

const filename = 'a.js'
const a = require(`./${filename}`)

a === require
    .cache[`${__dirname}/${filename}`]
    .exports // true

Summary

  1. Variables in a file (a module) are constrained to that scope. This is why you see so much top-level variable declaration in Node.
  2. module.exports is an object that the require function returns when it receives the module’s name as an input. exports is a reference to that object.
  3. Modules are cached the first time they’re read.◾

Sources

  1. Node.js docs
  2. Node.js Design Patterns