Create your own modules in NodeJS

If you haven't already set up your Node sample project, you can quickly do so by following these instructions

In the previous step of this project, we used NPM to download a few modules from the NPM website. You can also create your own modules. Organizing your code into modules is an important strategy for developers. Modules are also called 'components' (or, in NodeJS we've also referred to them as 'packages'). Component based design is the practice of organizing your code into components/modules. This makes your code more flexible, maintainable, and easier to test.

This article is a good overview of component based design.

Create a 'grading helper' module

Create a folder named modules inside the project folder.

Inside the modules folder, create a file named grading-helpers.js and put this code into it:

/**
Takes a test score (a number) as a parameter, and returns a letter grade.
The letter grade should be determined by the score like so:
90 - 100 should return "A"
80 - 89 should return "B"
70 - 79 should return "C"
60 - 69 should return "D"
A score lower than 60 should return "F"

@param {number} score       The score to be converted into a letter grade
@return {string}            The letter grade for the score
*/

function calculateLetterGrade(score){
    if(score >= 90){
        return "A";
    }else if(score >= 80){
        return "B";
    }else if(score > 70){
        return "C";
    }else if(score > 60){
        return "D"
    }else{
        return "F"
    }
}

/**
Takes an array of test scores (numbers) and returns the average for them.

@param {array<number>} scores     The array of scores to be used in 
                                  calculating the average

@return {number}                  The average of the scores
*/
function calculateAverageScore(scores){
  let total = 0;
  scores.forEach(s => total += s);
  return total/scores.length;
}

exports.calculateLetterGrade = calculateLetterGrade
exports.calculateAverageScore = calculateAverageScore

This module consists of two functions. The last two lines 'export' each function, which allows other files to 'import' the functions (if we did not export the functions, then they would be private, and could only be invoked from inside the grading-helpers.js file). A NodeJS module implicitly has an 'exports' object, and any property that you add to it will be exported. So the last two lines in the module add properties to the exports object.

If you look closely at the code, you may spot a bug or two. Hopefully we'll address these issues when we talk about testing our code.

I included proper documentation for each function in the comments. In a real-world project, you should add comments that give a brief description of the each function, as well as a description of it's paramters and return value.

Now that we've created a module, and exported the parts that can be shared with other files in the project, let's create an example that uses it.

In the samples folder, create a file named grading-example.js, and put this code in it:

const {calculateLetterGrade, calculateAverageScore} = require('../modules/grading-helpers');

console.log(calculateLetterGrade(93));

const testScores = [90, 80];
console.log(calculateAverageScore(testScores));

The first line imports both functions from the module, and the rest of the lines should be familiar to you.

To run the example program, open the terminal and run this command (make sure the terminal is in the project folder):

node samples/grading-example.js

You should see the return values of our functions in the console log.

We have been using the implicit exports object to export parts of a module, and the require() function to import parts of a module. But there is another way that you can do this, which is becoming more popular. The modules we have been creating are known as 'CommonJS' modules and the newer approach is knonw as 'ES6'. In the future we'll explore ES6 modules, but to avoid confusion when you are learning, we'll continue to use CommonJS modules in this project.

We started our journey into NodeJS by using two modules that are 'built-in' to it. The fs and http modules come with NodeJS when you install it. Then we used NPM to install modules that help us work with markdown files (the gray-matter and markdown-it packages). Finally, we created our own module and used it in the grading-example program.

Create a markdown-helpers module

Now we'll create another module, which will be very important because we'll use in our final project. One of the great thing about modules, is that they can be re-used in any Node project.

Add a file named markdown-helpers.js to the modules folder, and put this code into it:

const matter = require('gray-matter'); // converts md file (with gray matter) into an object
const md = require("markdown-it")({html:true});// html:true allows you to put HTML tags in the markdown files

/**
 * Converts a markdown file into an object that includes properties
 * for the file's front matter, a 'content' property which consists
 * of the files' markdown code, and an 'html' content which consists
 * of the markdown code that has been converted to HTML
 * 
 * @param {string} filePath   The path to the markdown file
 * @returns {object}
 */
function convertMarkdown(filePath){
  // TODO: Put code here
}

/**
 * Takes a path to a markdown file, reads the front-maktter (metadata) 
 * and returns an object that that includes the title, author, published
 * date of the file. 
 * @param {string} filePath   The path to the markdown file 
 * @returns {object}          An object that has a title, author, and published property
 */
function getMarkdownMetaData(filePath){
  // TODO: Put code here
}

exports.convertMarkdown = convertMarkdown;
exports.getMarkdownMetaData = getMarkdownMetaData;

This module imports the following NPM packages/modules:

  1. gray-matter for converting the contents of a markdown file into a JavaScript object (we used the variable name matter to store the 'gray-matter' object)
  2. markdown-it for convertint markdown code into HTML code ( we used the variable md to store the markdown-it object)

The module then defines two functions, which we'll add code to soon.

Finally, the module exports the two functions so that they can be 'imported' and used in other JavaScript files.

Now, update the convertMarkdown() function in the markdown-helpers module to look like this:

function convertMarkdown(filePath){
  const obj = matter.read(filePath);
  if(obj){
      obj.html = md.render(obj.content);   
  }
  return obj;
}

Now we'll create a sample program to test out the function.

Create a file named markdown-helper-tests.js in the samples, and put this code into it:

const {convertMarkdown, getMarkdownMetaData} = require("../modules/markdown-helpers");

const filePath = __dirname + "/sample.md"
console.log(convertMarkdown(filePath));

Run the test program by entering this in the terminal (make sure you run the command from the project folder):

node samples\markdown-helper-tests.js

In the console, you should see an object that has various properties for the front-matter metdata, a content proeprty which displays the markddown code for the file, and an html property which displays the markdown code that has been converted to HTML.

Now we'll start on the getMarkdownMetaData() function. Update the code to look like this:

function getMarkdownMetaData(filePath){
  const obj = matter.read(filePath);
  const metaData = {
      title: obj.data.title || "No Title",
      author: obj.data.author || "No Author",
      published: obj.data.published || false
  }
  return metaData;
}

Now add this code to the markdown-helper-tests program:

console.log(getMarkdownMetaData(filePath));

Now, when you run the test program (node samples\markdown-helper-tests.js), you should see an object in the console log that includes properties for the metadata (front-matter) in the markdown file. You might be wondering about the the published property, we'll revisit that when we get to the final project.

It would be a really good idea to step through code we've just writen with the JavaScript debugger.