Organizing Code with Modules
So far, most of our JavaScript has lived in a single file. That works fine for a short script, but real programs grow. Once a file reaches hundreds of lines, finding things becomes hard, and copying the same helper functions into several files gets messy.
Node.js solves this with modules. A module is simply a file that contains JavaScript code and shares the parts you choose with other files. Think of each file as a room with a door: by default, nothing inside is visible from outside. You decide what to put in the doorway.
Why Modules Matter
Modules give you three practical benefits:
- Reuse. Write a function once, use it in many files.
- Clarity. Each file focuses on one job, so you can find code faster.
- Safety. Variables in one file stay private to that file. They cannot collide with variables in another file.
Sharing Code with module.exports
By default, a module shares nothing. You share something by assigning it to module.exports, which is a built-in object that represents "what this file offers to others."
Here is a small file called greet.js:
// greet.js
function greet(name) {
return `Hello, ${name}!`;
}
module.exports = greet;The word function here creates a reusable piece of code that takes a name and returns a greeting. The last line says: "When someone imports this file, hand them the greet function."
Bringing Code In with require
To use another file's exports, call the built-in require function with the path to that file. A path starting with ./ means "in the same folder as this file."
// app.js
const greet = require('./greet');
console.log(greet('Node'));Run it from the terminal:
node app.jsYou will see Hello, Node! printed. The const greet line stores whatever greet.js exported, so you can call it just like any function you wrote yourself.
You can export more than one thing, too. Export an object whose properties are the items you want to share:
// math.js
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }
module.exports = { add, subtract };// app.js
const { add, subtract } = require('./math');
console.log(add(2, 3)); // 5
console.log(subtract(5, 2)); // 3The curly braces in const { add, subtract } pull out just those named properties. This pattern is called destructuring — unpacking values from an object into separate variables.
What Stays Private
Everything you do not export stays inside the file. Suppose greet.js also had a variable like const secret = 'shh'. Other files could never read it, even with require. This is why modules make code safer: each file controls its own boundary.
How require Finds Things
Two cases cover most of what you need:
- Your own files: use a relative path like
require('./greet'). - Installed packages: use just the name, like
require('express'). We will install packages in the next chapter.
If Node cannot find what you asked for, it stops the program with a "Cannot find module" error. Check the path and the file name first — forgetting the ./ for your own files is the most common mistake.
The Big Picture
Modules are the foundation for everything that follows. When you install a package with npm, you use it with require. When you read files, you require the fs module that comes with Node. Once this pattern clicks, the rest of Node feels familiar.
Exercise
In app.js, you write require('greet') instead of require('./greet'). What likely happens, and why?
Show answer
Node throws a "Cannot find module" error. Without the ./ prefix, Node treats greet as an installed package name rather than a file in the same folder.
Exercise
You have a helper function formatDate in dates.js that another file needs. What two steps make that possible?
Show answer
In dates.js, add module.exports = formatDate; (or include it in an exported object). In the other file, call const formatDate = require('./dates'); to import it.
Spotted a mistake, or code that does not work?