Node.js - module.exports vs exports
Tags: node
Published: 2023-05-09 13:17:06

[Photo](https://www.pexels.com/photo/red-container-ship-under-white-clouds-8937118/) by Julia Volk
<br/>
I've recently started reading the excellent "*Node.js Design Patterns*" book by Mario Casciaro and Luciano Mammino. The second Chapter of Book discusses the Node.js module system and provides an explanation of the CommonJs modules system through a minimal example implementation.

Though a fairly straightforward concept I thought there was some potential for confusion regarding the discussion of `module.exports` versus `exports` and although the authors do address this I saw no harm in reiterating the point in this post.

We'll start by briefly covering the idea of software modules followed by looking at how to use the JavaSCript CommonJS module system with example implementations, and then finally we'll explore the important caveat of `exports` by taking a peek under the hood at how the CommonJS module system actually works.
<br/>
### 1. So what are modules?


Modules are a key tenet behind programming languages they provide an explicit way of encapsulating data and functions from other areas of our code. They also encourage code re-usability and by wrapping modules into packages and libraries they provide a convenient way of sharing and distributing code.

In modern JavaScript there are two standard module systems, ECMAScript modules (ES modules) and CommonJS. Although CommonJS is certainly in decline relative to the introduction of ES modules in 2015 it is certainly still relevant in the current developer landscape and you have likely or will come across it in many Node.js code bases.
<br/>
### 2. **How do we import and export modules with CommonJS?**

Two foundational concepts in CommonJS are `module.exports` and `require`. Simply put, `module.exports` allows us to export a module, and `require` is a resolving function that allows us to import an exported module from a specified file. The example implementation below demonstrates how we can export a single `add` function from the `calculate.js` file and import it into the `main.js` file.


```jsx
// calculate.js

function add(num1, num2) {
    return num1 + num2;
}

module.exports = add;
```

```jsx
// main.js

const calculateAddition = require("./calculate");
const result = calculateAddition(2, 2);
console.log(result);

// result:
// 4

```

Notice how when exporting via this method we lose the original exported function name `add` and instead, we pass a reference to the exported function to the variable `calculateAddition`. If we didn't want to lose the function name we could instead assign an object literal containing the add function to `module.exports`

```jsx
// calculate.js

module.exports = {add};
```

or equivalently we can use the dot notation to append an additional property to module.exports

```jsx
// calculate.js

module.exports.add = add;
```

To import and access this function we can use the following code

```jsx
// main.js

const calculate = require("./calculate");
const result = calculate.add(2,2);
```

Additionally, we can change the name of the exported function

```jsx
// calculate.js

module.exports.addition = add;
```

```jsx
// main.js

const calculate = require("./calculate");
const result = calculate.addition(2,2);
```

We can also easily export multiple functions, classes, primitives, and objects which are all accessible on the single exported object literal.

```jsx
// calculate.js

function add(num1, num2) {
    return num1 + num2;
}

function sub(num1, num2) {
    // some code logic ...
}

function multiply(num1, num2) {
    // some code logic ...
}

const someConstantValue = 8;

const calculateObject = {
    a: 5,
    b: 6
};

class CalculationClass{
    calcSquareRoot(){
        // some code logic ...
    }
}



module.exports = {
    add,
    sub,
    multiply,
    someConstantValue,
    calculateObject,
    CalculationClass
}
```


Anything that isn't explicitly exported remains only accessible in the file in which it was declared. This means it will be hidden from any other file importing it and allow us to encapsulate private logic and data, this is a common pattern in JavaScript and is known as the "*revealing module pattern*".


Instead of `module.exports` we could also use the alias `exports` though this has an important caveat which we will cover in the next section. An example of how we can export both a `subtract` and `add` function using the `exports` keyword can be seen below.


```jsx
// calculate.js

exports.add = add;
exports.subtract = subtract;
```

And to import and use these functions

```jsx
// main.js

const calculate = require("./calculate");
const additionResult = calculate.add(2,2);
const subtractionResult = calculate.subtract(2,2);
```
&nbsp;&nbsp;<br/>
### 3. **What is the important caveat of using 'exports'?**

Let's go back to the first example where we assigned the `add` function to module.exports and try using the `exports` alias instead

```jsx
// calculate.js

function add(num1, num2) {
    return num1 + num2;
}

// replacing
// module.exports = add;

// with
exports = add;
```

If we attempt to import this in another file you'll get the following error

```jsx

// main.js

const add = require("./calculate"); // 2.
const result = add(2, 2);
console.log(result);

// result:
// TypeError: add is not a function
```

So why is that? To answer the question we'll need to take a brief look under the hood at how the module system in CommonJS works.

Every JS file in a Node.js application is actually a module. Before any code execution, Node.js will wrap all the code in each JS file inside a function wrapper. This function wrapper ensures that all functions, classes, variables, and objects remain private unless explicitly stated otherwise. Each function wrapper is passed the following parameters that are accessible to the code written in the file, `exports`, `require`, `module`, `__filename`, and `__dirname` .

The `module` parameter is simply an object that represents the current module and contains multiple properties including `exports`. The default value of the key `exports` in `module` is simply an empty object `{}`. As we saw in previous examples, export values can either be appended to this object using the dot notation

```jsx
module.exports.add = add;
module.exports.sub = subtract;
```

or can be assigned a new value e.g. Classes, Objects, functions, variables using

```jsx
module.exports = add;

// or

module.exports = {add};
```

So why can't we assign values directly to the `exports` parameter? Very simply this is because `exports` is a reference to the `module.exports` object. So we can append new properties to the `module.exports` object, for example using

```jsx
exports.add = add;
exports.sub = sub;
```

However, as the `exports` exports parameter is simply a reference if we attempt to assign it a new object using

```jsx
exports = add;
```

All we're doing is changing the reference of the object the `exports` parameters is pointing to rather than actually modifying the properties exported from the module. 

<br/>
### 4. Summary

- Modules provide a convenient way for us to encapsulate our code within tightly coupled units that facilitate code reuse and sharing.

- ES Modules and CommonJS represent the most common module systems used in modern JavaScript.

- In CommonJS, we can use either `module.exports` or `exports` in any JS file to export values from a module and we use `require` to import values from another module.

- Anything we don't explicitly export remains only accessible in the file in which it was declared, in other words 'encapsulated'.

- `module.exports` represents the actual value of the exported values in a module and `exports` is simply a reference to `module.exports`.

- We can append new values e.g. functions, objects, and primitives to either `module.exports` or `exports` using dot notation but we can only assign new values to `module.exports`.


- Valid export examples
    - `module.exports = {aFunction, anotherFunction}`
    - `module.exports = aFunction`
    - `module.exports.aFunction = aFunction`
    - `module.exports.renamedFunction = aFunction`
    - `exports.aFunction = aFunction`
    - `exports.aFunction = {aFunction}`
- Invalid export examples
    - `exports = {aFunction, anotherFunction}`
    - `exports = aFunction`