Getting started with Node.js modules: require
, exports
, imports
, and beyond.
Modules are a crucial concept to understand Node.js projects. In this post, we cover Node modules: require
, exports
and, the future import
.
Node modules allow you to write reusable code. You can nest them one inside another. Using the Node Package Manager (NPM), you can publish your modules and make them available to the community. Also, NPM enables you to reuse modules created by other developers.
We are using Node 12.x for the examples and ES6+ syntax. However, the concepts are valid for any version.
In this section, we are going to cover how to create Node modules and each one of its components:
- Require
- Exports
- Module (module.exports vs. export)
- Import
Require
require
are used to consume modules. It allows you to include modules in your programs. You can add built-in core Node.js modules, community-based modules (node_modules
), and local modules.
Let’s say we want to read a file from the filesystem. Node has a core module called ‘fs’:
1 | const fs = require('fs'); |
As you can see, we imported the “fs” module into our code. It allows us to use any function attached to it, like “readFile” and many others.
The require
function will look for files in the following order:
- Built-in core Node.js modules (like
fs
) - NPM Modules. It will look in the
node_modules
folder. - Local Modules. If the module name has a
./
,/
or../
, it will look for the directory/file in the given path. It matches the file extensions:*.js
,*.json
,*.mjs
,*.cjs
,*.wasm
and*.node
.
Let’s now explain each in little more details with
Built-in Modules
When you install node, it comes with many built-in modules. Node comes with batteries included ;)
Some of the most used core modules are:
- fs: Allows you to manipulate (create/read/write) files and directories.
- path: utilities to work with files and directories paths.
- http: create HTTP servers and clients for web development.
- url: utilities for parsing URLs and extracting elements from it.
These you don’t have to install it, you can import them and use them in your programs.
NPM Modules
NPM modules are 3rd-party modules that you can use after you install them. To name a few:
- lodash: a collection of utility functions for manipulating arrays, objects, and strings.
- request: HTTP client simpler to use than the built-in
http
module. - express: HTTP server for building websites and API. Again, simpler to use than the built-in
http
module.
These you have to install them first, like this:
1 | npm install express |
and then you can reference them like built-in modules, but this time they are going to be served from the node_modules
folder that contains all the 3rd-party libraries.
1 | const express = require('express'); |
Creating your own Nodejs modules
If you can’t find a built-in or 3rd-party library that does what you want, you will have to develop it yourself.
In the following sections, you are going to learn how to do that using exports
.
Exports
The exports
keyword gives you the chance to “export” your objects and methods. Let’s do an example:
1 | const PI = 3.14159265359; |
In the code below, we are exporting the area
and circumference
functions. We defined the PI
constant, but this is only accessible within the module. Only the elements associated with exports
are available outside the module.
So, we can consume it using require
in another file like follows:
1 | const circle = require('./circle'); |
Noticed that this time we prefix the module name with ./
. That indicates that the module is a local file.
Module Wrapper
You can think of each Node.js module as a self-contained function like the following one:
1 | (function (exports, require, module, __filename, __dirname) { |
We have already covered exports
and require
. Notice the relationship between module.exports
and exports
. They point to the same reference. But, if you assign something directly to exports
you will break its link to module.exports
— more on that in the next section.
For our convenience __filename
and __dirname
are defined. They provide the full path to the current file and directory. The latter excludes the filename and prints out the directory path.
For instance, for our ./circle.js
module, it would be something like this:
__filename
:/User/adrian/code/circle.js
__dirname
:/User/adrian/code
Ok, we have covered exports
, require
, __filename
, and __dirname
. The only one we haven’t covered is module
. Let’s go for it!
Module.exports vs. Exports
The module
is not global; it is local for each module. It contains metadata about a module like id, exports, parent, children, and so on.
exports
is an alias of module.exports
. Consequently, whatever you assign to exports
is also available on module.exports
. However, if you assign something directly to exports, then you lose the shortcut to module.exports
. E.g.
1 | class Cat { |
Try the following case with exports
and then with module.exports
.
1 | const Cat = require('./cat'); |
To sum up, when to use module.exports
vs exports
:
Use exports
to:
- Export named function. e.g.
exports.area
,exports.circumference
.
Use module.exports
to:
If you want to export an object, class, function at the root level (e.g.
module.exports = Cat
)If you prefer to return a single object that exposes multiple assignments. e.g.
module.exports = {area, circumference};
Imports
Starting with version 8.5.0+, Node.js supports ES modules natively with a feature flag and new file extension *.mjs
.
For instance, our previous circle.js
can be rewritten as circle.mjs
as follows:
1 | const PI = 3.14159265359; |
Then, we can use import:
1 | import { area, circumference } from './circle.mjs'; |
And, finally you can run it using the experimental module feature flag:
1 | node --experimental-modules main.mjs |
If you don’t like experimental modules, another alternative is to use a transpiler. That converts modern JavaScript to older versions for you. Good options are TypeScript, Babel, and Rollup.
Troubleshooting import
and require
issues
Experimental Flag
If you don’t use the experimental flag node --experimental-modules
and you try to use import
you will get an error like this:
1 | internal/modules/cjs/loader.js:819 |
File extension .mjs vs .js (or .cjs)
If you have a *.mjs
file you cannot use require
or it will throw an error (ReferenceError: require is not defined
).
.mjs
is for import
ECMAScript Modules and .js
is for regular require
modules.
However, with *.mjs
you can load both kinds of modules!
1 | import { area, circumference } from './circle.mjs'; |
Notice that cat.js
is using commonJS modules.
Summary
We learned about how to create Node.js modules and used it in our code. Modules allow us to reuse code easily. They provide functionality that is isolated from other modules. The require
function is used to load modules. The exports
and module.exports
allow us to define what parts of our code we want to expose. We also explored the difference between module.exports
and exports
. Finally, we took a quick pick about what’s coming up for modules using imports
.