Back to Blog
Lesson 6 of the Node.js: Build Your First Server & CLI course
Node.jsJuly 17, 20264 min read

Introduction to Modules: Organizing Your Node.js Code

Master Node.js modules to keep your backend code clean. Learn the require function, encapsulation, and how to split your project into manageable files.

Node.jsmodulesbackend developmentprogramming best practices
HTML code displayed on a screen, demonstrating web structure and syntax.

Previously in this course, we covered managing dependencies to pull in third-party libraries. While external packages are powerful, the true secret to building a maintainable REST API lies in how you organize your own source code. Today, we’re moving beyond writing everything in a single script by exploring the Node.js module system.

Understanding Modularity

In the early days of programming, we often wrote "spaghetti code"—thousands of lines in a single file where everything was tightly coupled. Modularity is the practice of breaking a large system into smaller, self-contained pieces called modules.

In Node.js, every file is treated as a module. This provides two key benefits:

  1. Encapsulation: You can hide internal helper functions and expose only what is necessary.
  2. Reusability: You can write a utility function once and use it across different parts of your application.

Creating Your First Module

To create a module, you define your logic in one file and export it. Let’s start by creating a simple math utility for our project.

Create a file named mathUtils.js:

JAVASCRIPT
// mathUtils.js
const add = (a, b) => a + b;
const subtract = (a, b) => a - b;

// We use module.exports to expose functions to other files
module.exports = {
  add,
  subtract
};

By assigning an object to module.exports, we define the "public API" of this file. Anything not attached to module.exports remains private to mathUtils.js.

Using the Require Function

Now that we have a module, we need a way to bring it into our main application. Node.js provides the require function for this exact purpose. It imports the exported object from a file path.

Create a file named app.js in the same directory:

JAVASCRIPT
// app.js
const math = require(CE9178">'./mathUtils');

console.log(math.add(5, 3));      // Output: 8
console.log(math.subtract(10, 4)); // Output: 6

When you run node app.js, Node.js resolves the path ./mathUtils, executes the code inside that file, and returns the object we assigned to module.exports.

Running Project: Advancing the API

For our REST API project, we should start separating our concerns. Instead of putting our server logic and our data-processing logic in the same file, let's create a logger.js module to handle console output consistently.

  1. Create logger.js:
JAVASCRIPT
const logInfo = (message) => {
  console.log(CE9178">`[INFO] ${new Date().toISOString()}: ${message}`);
};

module.exports = { logInfo };
  1. Update your main file to use it:
JAVASCRIPT
const { logInfo } = require(CE9178">'./logger');

logInfo(CE9178">'Starting the server...');

Hands-on Exercise

  1. Create a new module named greetings.js that exports a function sayHello(name).
  2. In your main file, require this module.
  3. Call the function to print "Hello, [Your Name]!" to the console.
  4. Verify your output by running the file via the terminal, as we learned when running scripts.

Common Pitfalls

  • Forgetting the path: When importing local files, always use ./ (for current directory) or ../ (for parent directory). If you just write require('mathUtils'), Node.js will look in your node_modules folder and fail.
  • Circular Dependencies: If a.js requires b.js, and b.js requires a.js, you create a loop that can lead to empty objects being returned. Keep your dependency graph simple.
  • Over-exporting: Don't export everything. Only expose the functions or variables that other files actually need to use.

FAQ

Q: Can I use require to import things like fs or path? A: Yes. Those are built-in core modules. You don't need a path like ./ for them; just require('fs') is enough.

Q: What is the difference between module.exports and exports? A: exports is just a shorthand reference to module.exports. However, if you reassign exports entirely (e.g., exports = { ... }), you break the connection. Stick to module.exports for clarity.

Q: Are these modules synchronous or asynchronous? A: require is synchronous. It blocks execution until the module is loaded. This is fine for startup, but avoid using it inside performance-critical loops.

Recap

We’ve learned that modules are the building blocks of Node.js applications. By using module.exports to define our interface and require to consume it, we can create modular, testable, and clean code. This structure is essential as our REST API grows in complexity.

Up next: We will dive into the nuances of CommonJS vs ES Modules and how to configure your project for modern JavaScript syntax.

Similar Posts