module.export & require | Nodejs

Episode 04 - Module Export & Requirements

Hey everyone! Welcome back to the Node.js tutorial series. Today we are going to learn about Module Export & Requirements!

We all know that keeping all the Node.js code in a single file is not good practice, right? We need multiple files to create a large project!

So in this chapter, we will explore how we can create those files and import concepts like modules and export requirements. Let's start!

What we will cover:

  • What are Modules?
  • require() Function
  • module.exports
  • Exporting Multiple Things
  • Destructuring Pattern
  • CommonJS vs ES Modules
  • Strict Mode vs Non-Strict Mode
  • Nested Modules Pattern
  • Importing JSON Files
  • Built-in Modules

What are Modules?

Let's say we have two files:

project/
├── app.js
└── xyz.js

These two files, app.js and xyz.js, have different code that is not related to each other.

In Node.js, we call them separate modules!

Q: How do you make two modules work together?

Using a require function!

Q: What is the require function?

In Node.js, the require() function is a built-in function that allows you to include or require other modules into your main modules.

Now, let's write our code using the require function!

Task: Execute xyz.js code from app.js

Our objective is to execute the code written in the xyz.js module by running the app.js module.

Steps:

  1. Open the app.js module
  2. First, include the xyz module using the require function
  3. Then, run the code using Node.js
// xyz.js

console.log("Hello from xyz.js!");
console.log("This code is in xyz module");
// app.js

require('./xyz');

console.log("This is app.js");
node app.js
OUTPUT:
Hello from xyz.js!
This code is in xyz module
This is app.js

The xyz.js code executed when we required it in app.js!

Important Concept - Pay Close Attention!

Disclosure: Before we proceed, I want to highlight that many Node.js developers may not be aware of the next concept I'm about to share, so please pay close attention!

Q: If I write a function in another module, can I use that function in a different module?

// sum.js

function calculateSum(a, b) {
    return a + b;
}
// app.js

require('./sum');

console.log(calculateSum(5, 3));  // Will this work?
OUTPUT:
ReferenceError: calculateSum is not defined

It will NOT work! 🤯

Reason (Very Important):

Modules protect their variables and functions from leaking by default!

Module Protection:
==================

sum.js                          app.js
┌─────────────────┐            ┌─────────────────┐
│                 │            │                 │
│  calculateSum() │ ────X────→ │  Can't access!  │
│  (Private!)     │            │                 │
│                 │            │                 │
└─────────────────┘            └─────────────────┘

Functions are PRIVATE by default!

Q: So, how do we achieve that?

We need to export the function using module.exports!

// sum.js

function calculateSum(a, b) {
    return a + b;
}

module.exports = calculateSum;

But it still won't work! 🤷‍♂️ Why?

Reason: You also need to import!

// app.js

const calculateSum = require('./sum');

console.log(calculateSum(5, 3));
OUTPUT:
8

Now it works! You need to both export AND import!

Q: How to export multiple things?

Suppose you need to export a variable x and a function calculateSum. How would you do this?

You can export both by wrapping them inside an object!

// sum.js

let x = "Hello from sum.js";

function calculateSum(a, b) {
    return a + b;
}

module.exports = {
    x,
    calculateSum
};
// app.js

const obj = require('./sum');

console.log(obj.x);
console.log(obj.calculateSum(5, 3));
OUTPUT:
Hello from sum.js
8

Destructuring Pattern - Very Common!

Many developers use destructuring as a common pattern to write cleaner and more efficient code. You'll encounter this technique frequently throughout your development journey! 😉

// app.js

const { x, calculateSum } = require('./sum');

console.log(x);
console.log(calculateSum(5, 3));
OUTPUT:
Hello from sum.js
8

Much cleaner! No need to write obj. every time!

Learn more about destructuring: https://courses.bigbinaryacademy.com/learn-javascript/object-destructuring/object-destructuring/

Important Note

When using the following import statement:

const { x, calculateSum } = require('./sum');

You can omit the .js extension, and it will still work correctly!

Node.js automatically resolves the file extension for you.

// Both work the same:
const sum = require('./sum.js');  // With extension
const sum = require('./sum');     // Without extension ✅

Summary So Far

To use private variables and functions in other modules, you need to export them. This allows other parts of your application to access and utilize those variables and functions.

Now go and practice and revise!

CommonJS vs ES Modules

Welcome back! Now, let's dive into another module pattern.

We've already studied CommonJS modules (CJS). Now, I'll introduce you to another type of module known as ES modules (ESM), which typically use the .mjs extension.

Two Module Systems:
===================

CommonJS (CJS)          ES Modules (ESM)
--------------          ----------------
require()               import
module.exports          export
.js extension           .mjs extension
Older (Node.js way)     Newer (Modern way)

Two Major Differences - Very Important!

1. Synchronous vs Asynchronous

  • CommonJS requires modules in a synchronous manner - the next line of code will execute only after the module has been loaded
  • ES modules load modules asynchronously - allowing for more efficient and flexible code execution

This distinction is a powerful feature and an important point to remember for interviews!

2. Strict Mode

  • CommonJS code runs in non-strict mode
  • ES modules execute in strict mode

ES modules enforce stricter parsing and error handling, making them generally safer and more reliable.

Overall, ES modules are considered better due to these advantages!

Using ES Modules

First, you need to create a new file called package.json (I will explain more about package.json later). For now, think of it as a configuration file.

To use ES modules, you must include the following in your package.json:

// package.json

{
    "type": "module"
}

This setting indicates that your code will use ES module syntax!

ES Modules Syntax

Exporting:

// sum.mjs (ES Module)

export let x = "Hello from ES Module";

export function calculateSum(a, b) {
    return a + b;
}

Importing:

// app.mjs

import { x, calculateSum } from './sum.mjs';

console.log(x);
console.log(calculateSum(5, 3));

Export multiple:

// math.mjs

const add = (a, b) => a + b;
const subtract = (a, b) => a - b;

export { add, subtract };

Default Export:

// greet.mjs

export default function greet(name) {
    return `Hello, ${name}!`;
}

// app.mjs
import greet from './greet.mjs';
console.log(greet("John"));

In the industry, you will still find CommonJS modules being used; however, in the next 2 to 3 years, there is expected to be a significant shift towards ES modules.

Strict Mode Demo - Impress Your Interviewer!

Let me show you a demo of strict mode and non-strict mode in case your interviewer asks about it—this will surely impress them!

CommonJS (Non-Strict Mode):

// app.js (CommonJS)

x = "Hello";  // No var, let, or const!

console.log(x);
node app.js
OUTPUT:
Hello

// It works! No error because CommonJS runs in non-strict mode

In a CommonJS module, you can define a variable without using var, let, or const, and the code will execute without throwing an error because it operates in non-strict mode.

ES Module (Strict Mode):

// app.mjs (ES Module)

x = "Hello";  // No var, let, or const!

console.log(x);
node app.mjs
OUTPUT:
ReferenceError: x is not defined

// Error! ES modules run in strict mode
// You cannot define variables without declaring them first!

In an ES module, if you try to execute the same code, it will throw an error because ES modules run in strict mode!

Q: What is module.exports?

module.exports is an empty object by default!

// test.js

console.log(module.exports);
OUTPUT:
{}

When you assign something to module.exports, you're replacing this empty object!

Another Common Pattern

Instead of writing:

module.exports = {
    x,
    calculateSum
};

Developers may prefer to write it like this:

module.exports.x = x;
module.exports.calculateSum = calculateSum;

Both achieve the same result!

Nested Modules Pattern

One more common pattern - Nested Modules!

Create a folder calculate, inside it create two files: sum.js and multiply.js

project/
├── app.js
└── calculate/
    ├── sum.js
    └── multiply.js
// calculate/sum.js

function calculateSum(a, b) {
    return a + b;
}

module.exports = calculateSum;
// calculate/multiply.js

function calculateMultiply(a, b) {
    return a * b;
}

module.exports = calculateMultiply;
// app.js

const calculateSum = require('./calculate/sum');
const calculateMultiply = require('./calculate/multiply');

console.log(calculateSum(5, 3));
console.log(calculateMultiply(5, 3));
OUTPUT:
8
15

Index.js Pattern - Very Common!

Let's create an index.js file in our calculate folder:

project/
├── app.js
└── calculate/
    ├── index.js    ← New file!
    ├── sum.js
    └── multiply.js
// calculate/index.js

const calculateSum = require('./sum');
const calculateMultiply = require('./multiply');

module.exports = {
    calculateSum,
    calculateMultiply
};

In the app.js file, you simply need to require from the calculate folder:

// app.js

const { calculateSum, calculateMultiply } = require('./calculate');

console.log(calculateSum(5, 3));
console.log(calculateMultiply(5, 3));
OUTPUT:
8
15

But why are we doing all this?

When you have a large project with hundreds of files, it's beneficial to group related files together and create separate modules.

In this case, the calculate folder serves as a new module that encapsulates related functionality, such as the calculateSum and calculateMultiply functions.

By organizing your code into modules, you improve:

  • Maintainability
  • Readability
  • Scalability

Making it easier to manage and navigate your codebase!

Importing JSON Files

Suppose you have a data.json file, and you want to import it into your JavaScript code.

You can do this easily using require!

// data.json

{
    "name": "John",
    "age": 25,
    "city": "New York"
}
// app.js

const data = require('./data.json');

console.log(data);
console.log(data.name);
console.log(data.age);
OUTPUT:
{ name: 'John', age: 25, city: 'New York' }
John
25

That easy! Node.js automatically parses the JSON for you!

Built-in Modules

There are some modules that are built into the core of Node.js!

One example is the util module. You can import it like this:

const util = require('util');

console.log(util);

We will learn more about built-in modules in upcoming episodes!

Quick Recap

Concept Description
require() Built-in function to import modules
module.exports Empty object by default, used to export
CommonJS (CJS) require/module.exports, synchronous, non-strict
ES Modules (ESM) import/export, asynchronous, strict mode
Destructuring const { x, fn } = require('./module')
index.js pattern Group related modules in a folder

Interview Questions

Q: What is require() in Node.js?

"require() is a built-in function in Node.js that allows you to include or require other modules into your main modules."

Q: Why can't we access functions from other modules directly?

"Modules protect their variables and functions from leaking by default. To share them, we need to explicitly export using module.exports and import using require()."

Q: What is the difference between CommonJS and ES Modules?

"CommonJS uses require/module.exports, loads synchronously, and runs in non-strict mode. ES Modules use import/export, load asynchronously, and run in strict mode. ES Modules are considered better and more modern."

Q: What is module.exports by default?

"module.exports is an empty object {} by default. When we assign something to it, we're replacing this empty object with our exports."

Q: Can we import JSON files in Node.js?

"Yes, we can import JSON files directly using require(). Node.js automatically parses the JSON and returns a JavaScript object."

Key Points to Remember

  • require() - import modules
  • module.exports - export from modules
  • Modules are private by default
  • Need to export AND import
  • Use destructuring for cleaner code
  • .js extension is optional
  • CommonJS = synchronous, non-strict
  • ES Modules = asynchronous, strict mode
  • Use "type": "module" in package.json for ESM
  • index.js pattern for grouping modules
  • Can import JSON files with require()

What's Next?

Now you understand how modules work in Node.js! In the next episode, we will:

  • Learn about built-in modules (fs, path, os)
  • Work with File System
  • Read and write files

Keep coding, keep learning! See you in the next one!