
We’re back and ready to do some refactoring of our CPU sensor so we can learn about Node.js modules and how to create them. Building small, focused modules is one of the key tenets of the Node.js philosophy as summarized in The Node Way:
Building small, single-purpose modules is at the heart of the Node.js philosophy. Borrowing from Unix, Node.js encourages composing the complex and powerful out of smaller, simpler pieces. This idea trickles down from entire applications (using the best tool for the job vs. a full suite) to how the tools themselves are built.
This architectural strategy also aligns well with the
Single responsibility principle used in object-oriented programming design which I have used as a C# and Java programmer.
Review of where we left off
If you have not read my first post in this series on Building a CPU Sensor, you might want to go ahead and do so now as it provides instructions for preparing your development environment and getting the first iteration of the CPU sensor created. In our last post we built a Cross Platform CPU sensor that enabled us to retrieve CPU loading numbers from either Windows or *nix-based systems. As a review, here is the source code of that finished project:
'use strict';
const os = require('os');
const cpu = require('windows-cpu');
const delaySeconds = 1;
const decimals = 3;
function loop() {
cpuLoad((error, result) => {
if (error) {
console.log('CPU load - error retrieving');
} else {
console.log('CPU load is ' + result);
}
});
setTimeout(loop, delaySeconds * 1000);
}
loop();
function cpuLoad(cb) {
if (process.platform === 'win32') {
cpu.totalLoad((error, cpus) => {
if (error) {
return cb(error);
}
// Average the CPU loads since may be multiple cores on the machine.
let sum = cpus.reduce((a, b) => {
return a + b;
});
let avg = sum / cpus.length;
cb(null, avg);
});
} else {
let linuxCpuLoad = os.loadavg()[0] * 100;
linuxCpuLoad = linuxCpuLoad.toFixed(decimals);
cb(null, linuxCpuLoad);
}
}
Refactor CPU load function into a separate module
Let’s break out the CPU load in a separate module to start us down the path of small, single purpose modules.
Create a file called cpu-load.js
in the root of your project directory and add the following code to this file:
'use strict';
const os = require('os');
const cpu = require('windows-cpu');
module.exports = function (decimals, cb) {
// Decimals is used for linux CPU load only since windows-cpu rounds to whole numbers.
decimals = decimals || 0;
if (process.platform === 'win32') {
cpu.totalLoad((error, cpus) => {
if (error) {
return cb(error);
}
// Average the CPU loads since may be multiple cores on the machine.
let sum = cpus.reduce((a, b) => {
return a + b;
});
let avg = sum / cpus.length;
cb(null, avg);
});
} else {
let linuxCpuLoad = os.loadavg()[0] * 100;
linuxCpuLoad = linuxCpuLoad.toFixed(decimals);
cb(null, linuxCpuLoad);
}
};
In order to make the function available as a module for other Node.js programs, we use the module.exports
command in line 5 to export the function. This article does a good job of delving deeper and explaining how module.exports
works in Node.js.
Notice that we also include the require
statement in this new module for the windows-cpu
npm module on line 3. This module is self-contained with everything it needs to function. We will longer need to include the require
statement for windows-cpu
in our main program.
Additionally, we add a decimals
parameter in our function on line 5 so we can control how many decimals appear to the right of the decimal point for the CPU load.
Be sure to save the changes you made to the cpu-load.js
file you just created.
Update main program file to invoke our new module
We are now ready to update our main program file and call our new module! Here is what the updated version looks like:
'use strict';
const cpuLoad = require('./cpu-load.js');
const delaySeconds = 1;
const decimals = 2;
function loop() {
cpuLoad(decimals, (error, result) => {
if (error) {
console.log('CPU load - error retrieving');
} else {
console.log('CPU load is ' + result);
}
});
setTimeout(loop, delaySeconds * 1000);
}
loop();
Our new main program is much simplified since we have moved the cpuLoad functionality to a separate module/file. You can see that we simply use a require
statement on line 2 to reference the module we created above.
Simulate an npm module
As a final step, we will utilize some npm magic to make our module appear to be an npm module coming from the npm public repository. In a future tutorial, we will learn how to create and publish a real module to npm. Let’s go!
Create new module folder and files
First, we’ll create a directory parallel to our project to host the module. Let’s call it universal-cpu-load
and position it in our folder hierarchy as follows:
P:.
\---node
+---cpu-sensor
\---universal-cpu-load
Navigate to the new universal-cpu-load
folder you just created:
p:\node\cpu-sensor> cd ..\universal-cpu-load
Run the following command to create a package.json
file. You can either accept the default values when prompted or enter values as you see fit:
p:\node\universal-cpu-load> npm init
Next, install the windows-cpu npm package in the new module folder and save the package as a dependency in your package.json
file.
p:\node\universal-cpu-load> npm install windows-cpu --save
We are ready to add the function to create our new Node module! Create an index.js
file in the root of the new module folder and enter the following:
'use strict';
const os = require('os');
const cpu = require('windows-cpu');
module.exports = function (decimals, cb) {
// Decimals is used for linux CPU load only since windows-cpu rounds to whole numbers.
decimals = decimals || 0;
if (process.platform === 'win32') {
cpu.totalLoad((error, cpus) => {
if (error) {
return cb(error);
}
// Average the CPU loads since may be multiple cores on the machine.
let sum = cpus.reduce((a, b) => {
return a + b;
});
let avg = sum / cpus.length;
cb(null, avg);
});
} else {
let linuxCpuLoad = os.loadavg()[0] * 100;
linuxCpuLoad = linuxCpuLoad.toFixed(decimals);
cb(null, linuxCpuLoad);
}
};
Link the module so it is available to the original Node.js program
Here’s where the npm simulation magic comes into play. Run an npm link
command from the root of the module directory you just created to create a symbolic link from your global npm packages folder to your module:
p:\node\universal-cpu-load> npm link
Next, change the directory to your main application and run another npm link
command containing the name of your module:
p:\node\cpu-sensor> npm link universal-cpu-load
After issuing this command, look in the node_modules
directory of your cpu-sensor
project folder. You will see a symbolic link to the universal-cpu-load folder
. This is what happened behind the scenes as a result of utilizing the npm link
command.
You can learn more about the npm link technique we employed above in this article.
Use the newly created module in your main program
Awesome – we’re ready for the final step of using our newly created module that appears to come directly from the public npm repository. Here’s an updated version of our program:
'use strict';
const cpuLoad = require('universal-cpu-load');
const delaySeconds = 1;
const decimals = 2;
function loop() {
cpuLoad(decimals, (error, result) => {
if (error) {
console.log('CPU load - error retrieving');
} else {
console.log('CPU load is ' + result);
}
});
setTimeout(loop, delaySeconds * 1000);
}
loop();
Notice that we can now use a require statement in line 2 to call our module without using a relative path (like we would for any standard npm module). Run the program and verify that it works as expected.
Come back again for our next tutorial. We will create a data visualization solution to monitor our CPU sensor in real-time and learn more valuable skills along the way!
Follow @thisDaveJ on X to stay up to date on the latest tutorials and tech articles.