In the last article we introduced Node.js module system, and we talked about require function that is used to load a module.
Today, we will dive deeper into Node.js module system and we will put forward some module definition patterns.
When we mention Node.js module system, we can define it by its two main characteristics:
– A mechanism for loading dependencies
– A tool for defining APIs (we will stick with it for the purpose of this article)
The main problem with APIs is the balance between private and public functionalities, since our goal is to enhance information hiding while considering other software qualities such as extensibility and code reuse. In this article we will see the different patterns used to apply all these requirements while creating modules.
Named Exports:
To expose a public API, we use named exports which is assigning the values we want to make available publically to the exports or module.exports object. The goal of using this pattern is to export only the functionalities that are meant to be used by our users and hide all the other details from them, let’s try to demonstrate this using some code:
We are going to create a module called chat, that will make conversation easy between a computer and a human!
We start with creating a new file called chat.js which is our module.
// file chat.js
exports.greet = (name) => {
console.log(`Hello ${name}`);
}
exports.ask = (question) => {
console.log(`Question : ${question}`);
}
This module has two exported functions, one is greet which takes a name as parameter and outputs a message and the other works with the same logic but it asks a question to the human. Now we will see how to use our module and take advantage of its functionalities:
We create a file main.js in the same directory as chat.js where we will load our module.
// file maine.js
// loading our module
const conversation = require(‘./chat’);
// using its functionalities
conversation.ask(‘What is your name?’);
conversation.greet(‘Gomytech’);
To use the functionalities ask and greet of our module chat, we just need to load it and invoke them, we’re not supposed to see or know the implementation details of the functions ask and greet; this is the simplest application of information hiding and making available only the parts that we want them to be public, this is how Node JS core modules works!
Exporting a function:
One other popular module definition pattern is reassigning the whole module.exports variable to a function. This pattern gives us the ability to expose only a single functionality which provides a clear entry point for our module.
This way is known as the substack pattern, let’s explore it using some code:
// file message.js
module.exports = (message) => {
console.log(`You have a message: ${message}`);
}
We can also extend this module by using the exported function as a namespace for other public APIs:
// file message.js
module.exports.notify = (message) => {
console.log(`You have a message: ${message}`);
}
Our module is created, let’s use it:
// file main.js
const notifyMe = require(‘./message’);
notifyMe(“Hello”);
notifyMe.notify(“Are you here?”);
You may think that exporting just one function looks like a limitation! In fact that’s a perfect way to focus on a single functionality which will be usually the core functionality of our module. The modularity of node.js encourages the use of Single Responsibility principle (SRP) which means that every module should have responsibility over a single functionality and it should be encapsulated by the module.
Exporting a class:
Modules also can export a class, actually exporting a class is a specialization of exporting a function the only difference is :
– exporting a class enables us to create a new instance using the constructor.
– enables us to extend the class prototype and produce new classes.
Take a look at the code below:
// file person.js
class Person {
constructor (name) {
this.name = name
}
greet(message) {
console.log(`[${this.name}]: ${message}`)
}
talk(message) {
this.log(`talk: ${message}`)
}
}
module.exports = Person
And to use our module:
// file main.js
const Person = require('./person)
const john = new Person('John')
jhon.talk('I am talking');
const beth = new Person(‘Bethanie’)
beth.greet(‘Hello’);
Exporting an instance:
Sometimes we need to share instances created from a class constructor across different modules. The following code shows an example of this pattern:
// file person.js
class Person {
constructor (name) {
this.count = 0
this.name = name
}
log (message) {
this.count++
console.log('[' + this.name + '] ' + message)
}
}
module.exports = new Person('BOT');
// file main.js
const person = require('./person);
person.log('I am a bot :3 ');
This way, every module that requires the person module will always receive the same instance, and this is in fact similar to the caching process in require function (we talked about that in the last article The module System). Another note to keep in mind, using this pattern doesn’t mean we are unable to create new instances from the exported class and we should only use the exported instance! We can create new instances by relying on the constructor property of the exported instance:
const customPerson = new Person.constructor(‘Alex’);
customPerson.log(‘Hello, I am Alex’);
As you can see, with Person.constructor() we can instantiate new Person objects.
Notes to keep in mind:
– A module can modify the global scope and any Object in it.
– A module can modify other modules, this technique is called monkey patching: it’s the modification of existing objects at runtime to change or extend their behaviour or apply fixes, let’s demonstrate that with some code:
// file custom.js
// ./myModule is another module
require('./myModule’).newFeature = function () {
console.log('This is a new functionality')
}
First we loaded our module with require, then we added a new functionality called newFeature. To start using this new feature:
// file main.js
require('./custom’);
const myModule = require('./myModule');
myModule.newFeature()
– we load the module where modifications took place.
– we load the original module
– we can now use the new feature added in the existing module.
This technique has side effects and it’s very dangerous, don’t use it if you don’t know all its side effects.
“It’s a module that allows you to mock HTTP responses in your tests. The way nock works is by monkey patching the Node.js http module and by changing its behavior so that it will provide the mocked response rather than issuing a real HTTP request. This allows our unit test to run without hitting the actual production HTTP endpoints, something that’s very convenient when writing tests for code that relies on third-party APIs.”
At this point we should have a complete understanding of Node.js Module system, CommonJS and some of the patterns that are used with it.