learnyounode Lesson 6 – Make it Modular

(Last Updated On: 2020-06-16)

In this lesson, we need to do almost exactly the same thing we did in the previous lesson.  However, we should encapsulate the previous solution into a function and assign the function to the exports property of the module object so that it can be called form another file.  To do this, we need to assign a function to the exports property of the module object.

module.exports = function() {}

Assigning our function to module.exports will allow us to assign our function to a variable in another program, very similar to how we assign the file system or path modules.

var solution = require('./mySolution')

Notice that instead of just assigning the module such as require(‘fs’), we actually need to specify the relative path to the module.  In the example above, we are saying that the relative path is the same as whatever program we are assigning the module in by prefixing ‘./’ to the name of our module.

The alternate solution provided for the previous lesson gets us 90% of the way there.  We only have to make a few changes to make it modular.

var fs = require('fs')
var path = require('path')

var dir = process.argv[2]
var filterStr = process.argv[3]

function getFiles(dir, filterStr, callback) {

  fs.readdir(dir, function (err, list) {
    if (err)
      return callback(err)

    list = list.filter(function (file) {
      return path.extname(file) === '.' + filterStr
    })

    callback(null, list)
  })
}

getFiles(dir, filterStr, function (err, list) {
  if (err)
    return console.error('There was an error:', err)

  list.forEach(function (file) {
    console.log(file)
  })
})

Official Solution

If you compare the official solution below to the solution from the previous lesson above, you will see that the solution has been split into two separate files.  In addition, we are no longer naming our function.  Instead, we are assigning it to the module.exports object.  You can assign a named function to exports.  However, this lesson calls for assigning an unnamed function to module.exports to replace what is currently assigned to the object.

solution_filter.js

var fs = require('fs')
var path = require('path')

module.exports = function (dir, filterStr, callback) {

  fs.readdir(dir, function (err, list) {
    if (err)
      return callback(err)

    list = list.filter(function (file) {
      return path.extname(file) === '.' + filterStr
    })

    callback(null, list)
  })
}

We are not calling our function in this program.  Instead, we are calling it in a separate program called solution.js.  Often times, the program used to call modules will be named main.js or index.js.

Note that we are assigning the filesystem and path modules in the same program that we declare our function.  We will not need to assign these modules again in our main program to use our function.

solution.js

var filterFn = require('./solution_filter.js')
var dir = process.argv[2]
var filterStr = process.argv[3]

filterFn(dir, filterStr, function (err, list) {
  if (err)
    return console.error('There was an error:', err)

  list.forEach(function (file) {
    console.log(file)
  })
})

In our main program, we are assigning our module to filterFn.  In the official solution the relative path to the module includes the ‘.js’ extension.  However, including the extension is optional.  We are also assigning the third and fourth arguments passed to process.argv from the command line to the variables dir and filterStr.

filterFn() is used to call our module function.  Dir, filterStr, and a callback function are passed as arguments.  The callback receives list as an argument.  Inside of the callback function, we are looping through list with the forEach() method.  We are passing a callback function to the forEach() method.  The callback accepts file as an argument, and logs file to the console with the console.log()method.

Module Contract

One important takeaway from this lesson apart from modularizing our code the node.js way, is the concept of a module contract.  In this case, our module contract is to export a single function that takes a directory, filter string, and callback as arguments.  The callback must be called once, with an error or data.  We are not modifying any global variables, and we are handling all errors by passing them to our callback to print an error message if an error occurs.

This concept is important moving forward because modules created and published to npm (node.js package manager) should behave as intended for anyone expecting that the module will produce the results of the module contract.  Building with modules is what node.js all about.

Lesson 7

Contents