Recently, I showed one way to use Node.js to collect filenames from a directory and its child directories. I described the technique as fundamental to various cleanup scripts. Such routines should be collected into reusable libraries (or modules, to use Node terminology). By doing so, you reduce maintenance, improve readability, and keep things organized.

This post shows how to create and use a custom module in Node.js.

Node.js module design is based on CommonJS module specification, though sources vary on the intent and history.

To begin, move the reusable functions to a separate JavaScript file (called mymodules.js in this example):

// myModule.js
module.exports.isBlank = function( inputValue ) {

   var blnResult = ( ( inputValue == undefined ) ||
                     ( inputValue == null ) || 
                     ( inputValue == "" ) );
   return blnResult;

}

module.exports.collectFilenames = function( strTargetPath ) {

   var oResults = new Object;
   oResults.paths = [];
   oResults.paths.push( strTargetPath );
   oResults.files = [];

   var fs = require( 'fs' );

   for ( var xDir = 0; xDir < ( oResults.paths.length ); xDir++ ) {
      var sCurrDir = oResults.paths[ xDir ];
      var aThisDir = fs.readdirSync( sCurrDir );

      for ( var xThisFile = 0; xThisFile < ( aThisDir.length ); xThisFile++ ) {

         var strFullFilename = sCurrDir + "/" + aThisDir[ xThisFile ];
         var xTFStat = fs.statSync( strFullFilename );

         if ( xTFStat.isFile() ) {
            oResults.files.push( strFullFilename );
         } else { if ( xTFStat.isDirectory() ) {
            oResults.paths.push( strFullFilename );
         } else {
            console.log( "Skipping ", strFullFilename, 
                         "; not a file or directory." );
         } }
      } 
   }
   return oResults.files;
}

You might have noticed that collectFilenames returns an array of strings in this version, rather than the object from the earlier post. That's a stylistic choice. I tend to limit return types to simple data types; I find this reduces problems in different clients (especially older ones). You'll notice I also limit the syntactical shortcuts for pretty much the same reason. I find this makes subtle errors easier to locate and results in clear, concise code.

Notice that the function declarations have changed. For websites, standalone JavaScript libraries are usually pulled into a webpage with the script src="..." statement. Such libraries usually shareable routines as functions:

function isBlank( inputValue ) { ... }
function collectFilenames = function( strTargetPath ) { ... }

When creating node modules, you assign functions to the module.exports object. Thus, your traditional function calls become:

module.exports.isBlank = function( inputValue ) { ... }
module.exports.collectFilenames = function( strTargetPath ) { ... }

As of Node v0.1.16, the module reference is optional for exports defined in the same file.

With the module in place, the calling code can be rewritten:

var nbt = require( "./myModule");
var fs = require( "fs" );

var strFilePath = process.argv[ 2 ];
if ( nbt.isBlank( strFilePath ) ) {
   console.log( "Declaration error: Cleanup directory not specified." );
   return;
}

var p = require( "path" );
var strFilePath = p.resolve( strFilePath );

var aFilenames = [];
aFilenames = nbt.collectFilenames( strFilePath );
console.log( JSON.stringify( aFilenames, undefined, 3 ) );

What remains is generally focused on the specific task.

Note: If you receive an "Error: Cannot find module 'example' error when you require your module, try adding a directory reference:

var nbt = require( "./myModule");

This example, assumes myModule is located in the same directory as the calling module. Adjust as needed to reflect your application structure.

In a future post, we'll explore some interesting design considerations that might affect the way you export module functions.

More info:

Vital stats:

  • First published: 12 Feb 2017.
  • Verified using Node.js, v7.4.0.