So, if you are creating, let’s say, a demo.js
file, this implies you are creating a module in Node. Basically modules help us encapsulating our code into manageable chunks.
Anything that we define in our module (i.e. in our JavaScript file) remains limited to that module only, unless we want to expose it to other parts of our code.
So, anything we define inside our module remains private to that module only.
Creating a module in Node is very simple, just create a file and you are good to go.
const sum = (a, b) => {
return a + b;
};
const result = sum(2, 3)
console.log(result)
That’s it, now you have a module in Node, nothing fancy, just simple creation of a file.
Node.js
, e.g. https
, os
, fs
, net
, etc.express
, or to deal with date and time we use moment
.Prior to modules in Node.js
or ES5 modules, the modularity in JavaScript was achieved using IIFE
(Immediately Invoked Function Expression), which is, as the name suggests, a function which is invoked immediately after it is defined.
(function () {
const sum = (a, b) => {
return a + b;
};
const result = sum(2, 3)
console.log(result)
})()
sum(5, 8) // ReferenceError: sum not defined
Now, if we run this code, we will get the output as 5
.
The function sum
is defined inside this IIFE and if any code outside that IIFE tries to access the sum
function, it will result in ReferenceError: sum is not defined
, i.e. the sum
function is private to this particular IIFE.
So, how do we access this sum
function outside of this IIFE?
const exportObj = {};
(function () {
const sum = (a, b) => {
return a + b;
};
const result1 = sum(2, 3)
console.log(result1) //5
exportObj.sum = sum;
})()
const result2 = exportObj.sum(5, 8)
console.log(result2) // 13
To expose our sum
function outside IIFE, we create an object (exportObj
) outside IIFE, then, through closure, we access that object inside our IIFE and assign our sum
function to one of its property.
After that, we call the sum
function on the exportObj
object outside the IIFE. This time, we are able to get result without any errors.
We have seen above that, to achieve modularity prior to Node and ES5, we used functions.
In Node.js
, this is the same way in which we achieve modularity, i.e. using functions only, but there is one gotcha — this wrapping function that wraps our code is not written by us but is automatically added by Node for us.
Let’s look at an example to understand it better.
Let’s say we defined one file, named sum.js
, with the following content:
const sum = (a, b) => {
return a + b;
};
const result = sum(2, 3)
console.log(result)
The code above, in sum.js
, is encapsulated by Node.js
into a function.
So, in Node.js
, this code is wrapped and looks something like this in our running environment:
(function (exports, require, module, __filename, __dirname) {
// Module code actually lives in here
const sum = (a, b) => {
return a + b;
};
const result = sum(2, 3)
console.log(result)
});
Everything is wrapped as we wrapped in our IIFE but, here, this wrapper function gets some arguments. We will discuss them in detail later in this post.
To check whether your code is wrapped in a function and whether we are receiving these arguments, or not. In JavaScript, we know that all functions receive an argument called arguments
, so, if we get arguments
in our code, it confirms that our code is inside a function.
console.log('Arguments given by react', arguments)
We can see that we get the output of arguments
(arguments is an array-like object, whose keys are numeric, which is passed to every function by default). So, it confirms that our code is wrapped inside a function and that function receives five arguments, which are given by Node.js
.
Let’s discuss these five arguments one-by-one.
This is an object used to expose our functionalities in one module, so these functionalities can be used in other modules.
We can expose anything, this can be a function, variable, constants, classes, etc. As we have done above in the How modularity worked before** **section, we have created a property on exportObj
and then assigned a value to it.
The same way we do it with exports
object — we create a property on the exports
object and then assign a value, or whatever you want to expose (variable, function, classes, constants), to that property.
const sum = (a, b) => {
return a + b;
};
const multiply = (a, b) => {
return a * b
};
exports.multiply = multiply;
Here, we expose the multiply function by assigning the function reference to a newly created multiply property on the exports
object, i.e. multiply function is only available outside this module, not the sum function.
Note: Do not provide a new reference to this exports
object, i.e. don’t assign a new object to the exports
argument. (We will discuss why not to do this.)
exports = {} // don't do this
const obj = {
firstName: 'Udit',
lastName: 'Tyagi'
}
// don't export items like this, as assigning will change the reference
exports = obj
//To correctly export, here we are not changing reference of exports
exports.name = obj
This is a function that we use to import or require the functionalities from other modules. It is a compliment to the exports
object, which is used to export functionalities. require
, on the other hand, is used to import those functionalities.
To require a module, we call the require
function with either the path of the module (absolute or relative), which starts with /
, ./
, or ../
in the case of local modules, or the name of the module in the case of core modules and third-party modules.
Then, it returns the exported content of the module that we require.
Note: Basically, we get the reference of the object module.exports
** **(we will discuss this) when we require a module.
const os = require('os'); //node's core module
const express = require('express') // third party module
const operations = require('./operations.js'); //local module
//Do something with these modules
const result1 = operations.multiply(2, 4);
console.log('Multiply Result: ', result1)// 8
const result2 = operations.sum(2, 3);// Error, as it is not exported.
console.log('Sum Result: ', result2)
In Code 7, we implemented two functions, sum
and multiply
, but we have exported only multiply
, so only that one is available outside of the operations.js
module. That is why we will get an error if we try to call sum
.
Node’s require
function has a lot more to offer than just importing the functionalities, we will dive deeper into this.
Module
This is the third argument passed, the module
variable is a reference to the object representing the current module. It has various useful properties which we can see in the terminal with console.log(module)
in any module.
Let’s say we do console.log(module)
in app.js
(Code 9) and operations.js
(Code 7). We get the following output:
console.log(module) in operations.js (pic 2)
console.log(module) in app.js (pic 3)
The module
object contains all the data regarding our module, such as: “Who is its parent? Who are its children? What are all the paths it took to resolve third-party modules? Is it completely loaded, or not?”
But the most important property of the module
object is the exports
property, we can also use this exports
property on the module to export our data, rather than using exports
arguments of the wrapper function.
const sum = (a, b) => {
return a + b;
};
const multiply = (a, b) => {
return a * b
};
module.exports = {
sum,
multiply
}
So, this is the second way of exporting functionalities out of our module.
Note: We will see the difference between exports
and module.exports
, and how they are connected to each other.
module
objectmodule.filename
is the fully resolved filename of the module.module.id
is the identifier for the module. Typically, this is the fully resolved filename, except for the main module, it is ‘.
’ (period), see pic 3. Main module is the module that spins up your Node application, e.g if we write node app.js
in the terminal, then app.js
is the main module.module.path
is the directory name of your name module.module.parent
is an object which refers to the parent module.module.children
is an array of all the children module objects.module.loaded
is a boolean property which tells us whether or not the module is done loading, or is in the process of loadingmodule.paths
is an array of all the paths that Node will look up to resolve a module.Some of you might have noticed in pic 2 and pic 3, this weird [Circular]
thing in module parent or children property. So, what is that?
Actually, [Circular]
defines a circular reference, as in pic 2, which prints out the module object of operations.js
. The parent property of the operations.js
module references the app.js
module.
Similarly, operations.js
is a child module of app.js
, so its children property should have a reference to the operations.js
module. And, similarly, the operations.js
module parent property again refers to the app.js
module, so it will go into this infinite loop.
To prevent this infinite loop, Node sees that, if any module’s parent or child is already loaded, it will not load them again and show this [Circular]
instead.
Pic 4
This is a variable that contains the absolute path of the current module.
Given two modules: a
and b
, where b
is a dependency of a
and there is a directory structure of:
/User/home/node_blog/a.js
/User/home/node_blog/node_modules/b/b.js
So, if we do console.log(<strong>filename)within b.js, we will get /User/home/node_blog/node_modules/b/b.js. If we doconsole.log(</strong>filename)
within a.js
, we will get /User/home/node_blog/a.js
.
The directory name of the current module. This is the same as the path.dirname()
of the __filename
.
So, for the above modules, a.js
and b.js
.
If we do console.log(<em>_dirname) within b.js, we will get /User/home/node</em>blog/node_modules/b/
and in a.js
, we will get /User/home/node_blog/
.
Now we have studied the basics of the module. From now on, we will dive deep into this topic. Bear with me a bit longer as there are various interesting things we are going to discuss
We use both module.exports
and exports
to export our functionalities out of our module.
But, there is a slight difference between them. Rather, I’ll say that they are not different but they are similar. The exports
object is just shorthand for module.exports
.
Inside Node, the exports
object refers to the module.exports
object. Which is somewhat like:
const exports = module.exports;
And, then, this module.exports
object is returned by the require
function when we require
in a module.
And that is the reason we don’t change the reference of the exports
object, as we wrote in Code 8 because, if we change the exports
object, that will no longer refer to the module.exports
, resulting in the functionalities not being exported from our module.
Can we use both module.exports
and exports
in a single module?
Yes, we can, but there are some subtleties we should keep in mind if we are using both.
Those are, when we use require
in any module, we get the module.exports
object and the exports
object referring to module.exports
, so it is necessary to maintain this reference.
const sum = (a, b) => {
return a + b;
};
const multiply = (a, b) => {
return a * b
};
module.exports = {
multiply
}
exports.sum = sum;
In this code, the sum will not be exported as we have changed the reference of module.exports
by assigning a new object to it but the exports
object now also refers to the previous reference of module.exports
.
To export the sum, we need to update the reference of the exports
object to the current reference of module.exports
.
const sum = (a, b) => {
return a + b;
};
const multiply = (a, b) => {
return a * b
};
module.exports = exports = {
multiply
}
exports.sum = sum;
It is not necessary that only a file can be a module that we require. Other than files, we also have folders as modules that we can require in.
Generally, a folder as a module is a module of modules, i.e. it contains various modules inside it to achieve functionality. This is what libraries do, they are organized in a self-contained directory and then they provide a single entry point to that directory.
There are two ways in which we can require a folder.
package.json
in the root of the folder, which specifies a main
module. An example package.json
file might look like this:{ "name" : "some-library",
"main" : "./lib/some-library.js" }
If this was in a folder at ./some-library
, then require('./some-library')
would attempt to load ./some-library/lib/some-library.js
.
This is the extent of Node.js
awareness of package.json
.
package.json
in the root directory of the module, or in package.json
if the main
entry is missing or cannot be resolved. Then, Node.js
will try to load index.js
or index.node
from that directory. For example, if there was no package.json
file in the above example, then require('./some-library')
would attempt to load:./some-library/index.js
./some-library/index.node
If these attempts fail, then Node.js
will report the entire module as missing with the default error:
Error: Cannot find module ‘some-library’.
In file modules, .js
file is also not the only module, we have .json
files and .node
files, they are also modules in Node.
When we require a module it is not necessary to give the file extension.
For example, if there is a some-file.js
file that we want to require and it is on the same level, we can require it as:
const someFile = require(‘./some-file’);
That is without specifying the extension.
While resolving the path of this file, Node follows a procedure.
It first looks for some-file.js
, if some-file.js
is not present, it will look for some-file.json
and if that is also not present, it will look for some-file.node
.
.js
files are interpreted as JavaScript text files, and .json
files are parsed as JSON text files, i.e. we get the JavaScript object. .node
files are interpreted as compiled add-on modules.
module.exports
Resolving the module goes through an order:
/
, ./
, or ../
, and if Node finds a match it will load it./
, ./
, or ../
, then Node will start looking for our module in all the directories specified in the module.paths
array (see pic 2 or 3) one-by-one. I.e. Node will start looking in our application parent directory node_modules
folder and if it is not found there, it moves to the parent directory, and so on, until the root of the file system is reached.After resolving a module, Node will load it and executes its code, but what if we want to only check whether we have a particular module or not, i.e. we don’t want to load and execute it, we just want to resolve it.
For that we use the resolve
function on require
:
const resolvedFilename = require.resolve(moduleToResolve);
Use the internal require()
machinery to look up the location of a module, but rather than loading the module, just return the resolved filename.
We have seen the order Node goes through to resolve a module and, also, it searches a path during the resolution of a module, as we discussed in point 3 above.
But, how do we programmatically know what all the paths are that Node searched to resolve our module? For that we use:
const allPaths = require.resolve.paths(moduleToResolve);
Returns an array containing the paths searched during the resolution of moduleToResolve
or null
, if the moduleToResolve
string references a core module, for example, http
or fs
.
For example, for the following directory structure:
Directory Structure (Pic 5)
If we write and execute the following code :
const momentPaths = require.resolve.paths('moment');
const expressPaths = require.resolve.paths('express');
console.log('Moment Paths', momentPaths);
console.log('Express Paths', expressPaths);
Output of Code 14 will be:
Pic 6
Modules are cached after the first time they are loaded. This means (among other things) that every call to require('foo')
will get exactly the same object returned, if it would resolve to the same file.
These modules are cached in an object in a key-value pair and we can reference that object using require.cache
.
But, if we delete any key from this object, then require
will reload the module next. There is also an exception with native addons. If we delete their key and try to reload them, it will result in an error.
For example, we have two files:
cacheFile1.js
console.log(‘Inside cache file 1’);
cacheFile2.js
console.log(‘Inside cache file 2’);
require('./cacheFile1');
require('./cacheFile2');
console.log('Trying to require cacheFile2 again');
require('./cacheFile2')
Output of code 15 (Pic 7)
In the output above, you can see that we get “Inside cache file 2” only one time, but we required it two times. This happens because, during the first time, only cacheFile1.js
andcacheFile2.js
are cached.
A main module is the module that spun up your application. For example, if we start our application by writing<em> </em>node app.js
, then app.js
is the main module of my application.
We have one more property on require, called require.main
*, *which is amodule
object referencing my main module, so if, in our code, we need to check whether the module is main or not and then we want to perform some actions, we can do:
if(require.main === module){
//Your Code goes here
}
Pic 8
So, a circular require happens when a.js
* *requires b.js
and b.js
requires a.js
.
When there are circular require()
calls, a module might not have finished executing when it is returned. Let’s take an example:
console.log('a starting');
exports.done = false;
const b = require('./b.js');
console.log('in a, b.done = %j', b.done);
exports.done = true;
console.log('a done');.
console.log('b starting');
exports.done = false;
const a = require('./a.js');
console.log('in b, a.done = %j', a.done);
exports.done = true;
console.log('b done');
console.log('main starting');
const a = require('./a.js');
const b = require('./b.js');
console.log('in main, a.done = %j, b.done = %j', a.done, b.done);
When main.js
loads a.js
, then a.js
, in turn, loads b.js
. At that point, b.js
tries to load a.js
.
To prevent an infinite loop, an unfinished copy of the a.js
exports object is returned to the b.js
module. b.js
then finishes loading and its exports
object is provided to the a.js
module.
By the time main.js
has loaded both modules, they’re both finished. The output of this program would thus be:
$ node main.js
main starting
a starting
b starting
in b, a.done = false
b done
in a, b.done = true
a done
in main, a.done = true, b.done = true
There is also a module
module inNode.js
, which is different from the module object we have studied so far. It provides some general utility methods when we are interacting with our module.
For example, it provides one utility property, called builtinModules
, to list out the name of all core modules of Node.js
, which we can use to check whether the module we are using is Node’s core module or not, and perform actions accordingly.
const builtin = require('module').builtinModules;
Thank you all for bearing with me till here. Various topics that we discussed here are not extensively used but, to excel in Node, we need to know how things actually work and the different functionalities they offer.
So, if you go and build an application, you have a better understanding of how modularity works and what all the things are that we can use.
☞ Running Serverless Node.js on Google Cloud Functions
☞ What Is Process.Env In Node.Js - Environment Variables In Node.Js
☞ Build a REST API to manage users and roles using Firebase and Node.js
☞ How to Send the Email Using the Gmail SMTP in Node.js
☞ Real-Time Chat App with Node.js, Express.js, and Socket.io
☞ Dockerizing a Vue Application
☞ Node js Express Upload File/Image Example
#node-js #javascript