DEV Community

Cover image for Everything You Need To Know About Node.js Modules
vikas mishra
vikas mishra

Posted on

Everything You Need To Know About Node.js Modules

Modules are small chunks of code or we can say — “ Module divides the codebase into small units, which can be used everywhere in the application”. Other programming languages also have their own module system like Java, Go, and PHP; it’s called Package; in Ruby it’s Unit.

Node.js currently comes with 2 different modules systems

1.Commonjs (CJS)

2.ECMAScript modules (ESM or ES modules)

The need for modules :

1.Having a way to split the codebase into multiple files.

  1. Allowing code reuse across different projects.

  2. Encapsulation (or information hiding). It is generally a good idea to hide implementation complexity and only expose simple interfaces with clear responsibilities.

  3. Managing dependencies. A good module system should make it easy for module developers to build on top of existing modules, including third-party ones.

We can discuss more the 2 types of modules in Node.js, but for now, let’s see how we can use the module system.

There are 2 main concepts of module system —

  • “require” is a function that allows you to import a module from the local filesystem
  • “exports” and “module.exports” are special variables that can be used to export public functionality from the current module

Note — At this point, we are not comparing the 2 modules. We just trying to understand the basic things.

There are some popular patterns for defining the modules and exporting them. By exporting a module, it is available throughout the application.

Let’s have a look at these patterns —

Named Exports:

The most basic concept is to expose a public API is using name export. Let’s take a look on the below code.

exports.info = (message) => { console.log(info: ${message}) } exports.verbose = (message) => { console.log(verbose: ${message}) }

Here we can see, we are exporting a function by giving it one name like info and verbose. The exported function is now available. We can use them like-

const logger = require(‘./logger’) logger.info(‘This is an informational message’) logger.verbose(‘This is a verbose message’)

This method allows us to export everything from the module. It makes API as public so everyone can all methods. So we can conclude, It should be used when we want all methods public. Now let’s move on to the next pattern.

Exporting a Function:

One of the most popular module definition patterns consists of reassigning the whole module.exports variable to a function. The main strength of this pattern is the fact that it allows you to expose only a single functionality, which provides a clear entry point for the module, making it simpler to understand and use; it also honors the principle of a small surface area very well. This way of defining modules is also known in the community as the substack pattern.

module.exports = (message) => { console.log(info: ${message}) }

This is a very powerful combination because it still gives the module the clarity of a single entry point (the main exported function) and at the same time it allows us to expose other functionalities that have secondary or more advanced use cases.

module.exports.verbose = (message) => { console.log(verbose: ${message}) }

This code demonstrates how to use the module that we just defined:

const logger = require('./logger') logger('This is an informational message') logger.verbose('This is a verbose message')

Exporting a Class:

A module that exports a class is a specialization of a module that exports a function. A module that exports a class is a specialization of a module that exports a function.

class Logger {
constructor (name) {
this.name = name
}
log (message) {
console.log(
[${this.name}] ${message})
}
info (message) {
this.log(
info: ${message})
}
verbose (message) {
this.log(
verbose: ${message})
}
}
module.exports = Logger

And, we can use the preceding module as follows:

const Logger = require('./logger')
const dbLogger = new Logger('DB')
dbLogger.info('This is an informational message')
const accessLogger = new Logger('ACCESS')
accessLogger.verbose('This is a verbose message')

Exporting a class still provides a single entry point for the module, but compared to the substack pattern, it exposes a lot more of the module internals. On the other hand, it allows much more power when it comes to extending its functionality.

Exporting an instance:
Instead of exporting a new class, we can expose its instance also.

class Logger {
constructor (name) {
this.count = 0
this.name = name
}
log (message) {
this.count++
console.log('[' + this.name + '] ' + message)
}
}
module.exports = new Logger('DEFAULT')

This newly defined module can then be used as follows:

const logger = require('./logger')
logger.log('This is an informational message')

One interesting detail of this pattern is that it does not preclude the opportunity to create new instances, even if we are not explicitly exporting the class. In fact, we can rely on the constructor property of the exported instance to construct a new instance of the same type:

const customLogger = new logger.constructor('CUSTOM')
customLogger.log('This is an informational message')

As you can see, by using logger.constructor(), we can instantiate new Logger objects. Note that this technique must be used with caution or avoided altogether. Consider that, if the module author decided not to export the class explicitly, they probably wanted to keep this class private.

Conclusion:

We have covered all types of exporting patterns provided by Node.js. The best one we can use all depends on our requirements and application architecture.

I hope you will find these article useful. Give it some claps to make others find it too!

Top comments (0)