Working with Modules in Node.js

Modules allow you to organize your code into reusable pieces. In this section, we'll explore how modules work in Node.js, exploring both the traditional CommonJS system and the newer ECMAScript Modules (ESM). We will also look at installing and importing modules from external sources like npm too.

Before we jump into the technical details, let's understand why modules are crucial:

  • Code Organization: Modules help you split your code into manageable chunks.
  • Reusability: You can use the same module for multiple application parts or even for different projects.
  • Encapsulation: Modules allow you to keep certain parts of your code private, exposing only what's necessary.
  • Dependency Management: Modules make it easy to manage and update external libraries on which your project depends.

Creating and Importing Local Modules

Let's start with the CommonJS module system, which has long been the standard in Node.js.

Create a file named greetings.js with the following content:

// greetings.js
function sayHello(name) {
    return `Hello, ${name}!`;
}

function sayGoodbye(name) {
    return `Goodbye, ${name}!`;
}

module.exports = {
    sayHello,
    sayGoodbye
};

Now, let's use this module in another file named app.js:

// app.js
const greetings = require('./greetings');

console.log(greetings.sayHello('Alice')); // Output: Hello, Alice!
console.log(greetings.sayGoodbye('Bob')); // Output: Goodbye, Bob!

Run this script using:

node app.js

In this example, module.exports exposes functions from the greetings.js module, and require imports them into app.js.

Exporting a Single Function or Object

If your module only needs to export a single function or object, you can directly assign it to module.exports:

// calculator.js
module.exports = function add(a, b) {
    return a + b;
};

You would then import and use it like this:

// app.js
const add = require('./calculator');

console.log(add(5, 3)); // Output: 8

ECMAScript Modules (ESM) in Node.js

Starting from Node.js version 20, you can natively use ECMAScript modules (ESM). This brings Node.js closer to the module system used in modern browsers.

To use ESM, you have a few options:

  1. Create your files with a .mjs extension
  2. Use .js extension and set "type": "module" in your package.json (we will cover this later)
  3. Use the --input-type=module flag when running Node from the command line

Let's rewrite our greetings module using ESM:

// greetings.mjs
export function sayHello(name) {
    return `Hello, ${name}!`;
}

export function sayGoodbye(name) {
    return `Goodbye, ${name}!`;
}

And now let's use it:

// app.mjs
import { sayHello, sayGoodbye } from './greetings.mjs';

console.log(sayHello('Alice')); // Output: Hello, Alice!
console.log(sayGoodbye('Bob')); // Output: Goodbye, Bob!

Run it with:

node app.mjs

Using ESM with .js Files

We haven't covered generating a package.json just yet, but you may have done this before for frontend projects.

If you have and you prefer to use .js extensions, you can set the type field in your package.json to "module":

{
  "type": "module"
}

Then, you can use ESM syntax in your .js files:

// greetings.js
export function sayHello(name) {
  return `Hello, ${name}!`;
}

// app.js
import { sayHello } from './greetings.js';

console.log(sayHello('World'));

Run it with:

node app.js

But we will dive into that next for a refresher and to ensure you understand how to set up a package.json.

Installing and Using Third-Party Modules

One of Node.js's strengths is its vast ecosystem of third-party packages available through npm (Node Package Manager). Let's walk through the process of installing and using a popular package.

First, let's create a new directory for our project and initialize it:

mkdir node-project
cd node-project
npm init

This will take you through some options to set as values for your package.json. You can hit the enter key to accept the default values.

To initiate a project and get all the defaults simply run:

npm init -y

This -y sets all the answers to their default values.

Once this is done, it creates a package.json file, which will keep track of your project's dependencies.

Installing a Package

Now, let's install a popular package called chalk, which is used for styling console output:

npm install chalk

This command does two things:

  1. It downloads the chalk package and its dependencies into a node_modules folder in your project directory.
  2. It adds chalk to the list of dependencies in your package.json file.

Using a Third-Party Package

Let's use chalk to enhance our greetings:

// app.js
const chalk = require('chalk');
const greetings = require('./greetings');

console.log(chalk.blue(greetings.sayHello('Alice')));
console.log(chalk.red(greetings.sayGoodbye('Bob')));

Run the script with:

node app.js

You should see the greetings printed in blue and red.

As an exercise, try and convert the code into

Managing Dependencies

The package.json file in your project root is essential for managing dependencies. It keeps track of all the packages your project needs. When you share or deploy your project, you don't need to include the node_modules folder. Instead, others can install all necessary dependencies by running:

npm install

This command reads the package.json file and installs all listed dependencies.

Choosing Between CommonJS and ESM

Node.js supports both CommonJS and ECMAScript modules, giving flexibility in your application structure. Here are some considerations:

  • CommonJS (require/module.exports) is widely used and supported in the Node.js ecosystem.
  • ESM (import/export) offers better consistency with browser JavaScript and enables features like top-level await.
  • ESM might be a good choice for new projects, especially those that may run in both Node.js and browser environments.
  • For existing projects or when working with packages that use CommonJS, you might stick with require and module.exports.

You can mix both in the same project: files ending with .cjs will always be treated as CommonJS, and files ending with .mjs will always be treated as ESM, regardless of package.json settings.

Some Quick Tips

  • Keep modules small and focused: Each module should have a single responsibility.
  • Use meaningful names: Name your modules and exported functions clearly to indicate their purpose.
  • Avoid circular dependencies: Be cautious about creating modules that depend on each other circularly.
  • Use default exports judiciously: While default exports can be convenient, named exports often provide better clarity and are easier to refactor.

Debugging Modules Tips

You might encounter issues like "Module not found" errors when working with modules. Here are some tips for debugging:

  • Double-check your file paths when using require or import.
  • Ensure you've installed all necessary packages (run npm install).
  • Check that you use the correct module system (CommonJS vs ESM) for your project setup. = Use the node --trace-warnings flag to get more detailed error information.

As you continue to work with Node.js, you'll create and use modules frequently. This modular approach will help you build more maintainable and scalable applications.

In the next section, we'll explore some of the useful built-in modules that Node.js provides, which will further expand your toolkit for Node.js development. These built-in modules leverage the module system we've discussed here, so you'll be well-prepared to use them effectively.

NodejsBeginner
Avatar for Niall Maher

Written by Niall Maher

Founder of Codú - The web developer community! I've worked in nearly every corner of technology businesses: Lead Developer, Software Architect, Product Manager, CTO, and now happily a Founder.

Loading

Fetching comments

Hey! 👋

Got something to say?

or to leave a comment.