Modules

Modules are partitioned files where we define our variables and functions. Values defined in modules are scoped to that specific module, constituting a unique name space. This avoids name clashes in large programs.

  • Every file in a Node application is considered a module.

  • The variables and methods in a module are equivalent to private properties and methods in object-oriented programming.

  • If you wish to use a function or variable defined in a module outside of its modular container you need to explicitly export it and make it public.

Structure of a module

Node keeps an internal record of the properties of a module. To see this we can log the property module to the console.

// index.js
console.log(module);

This gives us:

Module {
 id: '.',
 path: '/home/thomas/repos/node-learning',
 exports: {},
 filename: '/home/thomas/repos/node-learning/index.js',
 loaded: false,
 children: [],
 paths: [
   '/home/thomas/repos/node-learning/node_modules',
   '/home/thomas/repos/node_modules',
   '/home/thomas/node_modules',
   '/home/node_modules',
   '/node_modules'
 ]
}

Exports

  • Whenever we export a property or method from a module we are directly targeting the exports property of the module object.
  • Once we add exports to a file they will be displayed under that property of the module object.
  • We can export the entire module itself as the export (typically used when the module is a single function or class) or individual properties.

Exporting a whole module

The example below is a module file that consists in a single function

module.exports = function (...params) {
  // function body
};

Note the module is unnamed. We would name it when we import:

const myFunction = require("./filenme");

Exporting sub-components from a module

In the example below we export a variable and function from the same module. Note only those values prefixed with exports are exported.

exports.myFunc = (...params) => {
  // function bod[]()y
};

exports.aVar = 321.3;

var nonExportedVar = true;

This time the exports are already name so we would import with the following:

const { myFunc, aVar } = require("./filename");

We can also do the exporting at the bottom when the individual components are named:

const myNamedFunc = (val) => {
  return val + 1;
};

function anotherNamedFunc(val) {
  return val * 2;
}

// This time we export at the bottom
exports.myNamedFunc = myNamedFunc;
exports.differentName = anotherNamedFunc; // We can use different names

// Or we could export them together
module.exports = { myNamedFunc, anotherNamedFunc };

The import is the same:

const { myNamedFunc, anotherNamedFunc } = require("./modules/multiExports");

Structuring modules

The techniques above are useful to know but generally you would want to enforce a stricter structure than a mix of exported and private values in the one file. The best way to do this is with a single default export.

Here the thing exported could be a composite function or an object that basically acts like a class with methods and properties.

Export a composite single function


module.exports = () => {
	foo() {...}
	bar() {...}
}

Export an object


module.exports = {
	foo : () => {...},
    bar: () => {...}
}

Both of these structures would be referred to in the same way when importing and using them.

Or you could export an actual class as the default. This is practically the same as the two above other than that you would have to use new to initiate an instance of the class.

export default class {
  foo() {}
  bar() {}
}

Every method and property within the export will be public by default, whether it is an object, class or function. If you wanted to keep certain methods/properties private, the best approach is to define them as variables and functions within the module file but outside of the export block.