Node.js Modules and the require System

Node.js uses a module system to keep code organised. Instead of putting everything in one giant file, you split your application into separate files โ€” each responsible for one thing โ€” and connect them using require and module.exports. This is the CommonJS module system, and it is what Node.js uses by default. Every file you create in your Express server is a module. Understanding how modules work is fundamental to reading and writing any Node.js code, and it determines how you structure your entire MERN backend.

What Is a Module?

In Node.js, every .js file is automatically treated as a module. Each module has its own private scope โ€” variables and functions defined in one file are not visible to other files unless you explicitly export them. This prevents naming conflicts and makes each file self-contained and testable.

Concept What It Does Keyword
Export Make something available to other files module.exports
Import Load an export from another file or package require()
Local module A file you wrote in your project require('./path/to/file')
Core module Built into Node.js โ€” no install needed require('fs'), require('path')
npm package Installed via npm into node_modules require('express')
Note: Node.js supports two module systems: CommonJS (require / module.exports) and ES Modules (import / export). CommonJS is the default for Node.js and is what all Express tutorials use. ES Modules are the standard in React (Vite). In this series we use CommonJS on the server and ES Modules on the client โ€” do not mix them in the same project without extra configuration.
Tip: When requiring a local file, always start the path with ./ (current directory) or ../ (parent directory). Without a leading dot, Node.js assumes you are requiring a core module or an npm package, not a local file. For example: require('./routes/posts') loads your local file, while require('express') loads the npm package.
Warning: require() is synchronous and caches modules after the first load. If you require the same file from ten different places, Node.js runs that file once and reuses the cached export for all subsequent calls. This is usually the behaviour you want โ€” but it means if a module has side effects (like opening a database connection), those side effects only run once, not once per require call.

Exporting and Importing โ€” Basic Patterns

// โ”€โ”€ Exporting a single value โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// server/src/config/db.js
const mongoose = require('mongoose');

const connectDB = async () => {
  await mongoose.connect(process.env.MONGODB_URI);
  console.log('MongoDB connected');
};

module.exports = connectDB;   // export a single function

// โ”€โ”€ Importing a single value โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// server/index.js
const connectDB = require('./src/config/db');   // .js extension is optional
connectDB();
// โ”€โ”€ Exporting multiple values โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
// server/src/utils/helpers.js
const slugify = (text) => text.toLowerCase().replace(/\s+/g, '-');
const capitalize = (text) => text.charAt(0).toUpperCase() + text.slice(1);

module.exports = { slugify, capitalize };   // export an object

// โ”€โ”€ Importing multiple values โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€
const { slugify, capitalize } = require('./utils/helpers');
console.log(slugify('Hello World'));    // hello-world
console.log(capitalize('mern stack')); // Mern stack

The require() Resolution Algorithm

When you call require('something'), Node.js resolves it in this order:

1. Is it a core module? (fs, path, http, crypto...)
   โ†’ Load the built-in module immediately

2. Does the path start with ./ or ../  or  / ?
   โ†’ It is a local file โ€” resolve relative to the current file
   โ†’ Try: something.js  โ†’  something.json  โ†’  something/index.js

3. Otherwise โ€” look in node_modules:
   โ†’ ./node_modules/something
   โ†’ ../node_modules/something
   โ†’ ../../node_modules/something   (walks up until root)
   โ†’ Throw MODULE_NOT_FOUND if not found anywhere

Structuring a MERN Server with Modules

server/
โ”œโ”€โ”€ index.js                  โ† requires config/db, routes, middleware
โ”œโ”€โ”€ src/
โ”‚   โ”œโ”€โ”€ config/
โ”‚   โ”‚   โ””โ”€โ”€ db.js             โ† exports connectDB function
โ”‚   โ”œโ”€โ”€ models/
โ”‚   โ”‚   โ””โ”€โ”€ Post.js           โ† exports Post Mongoose model
โ”‚   โ”œโ”€โ”€ controllers/
โ”‚   โ”‚   โ””โ”€โ”€ postController.js โ† exports { getAllPosts, createPost, ... }
โ”‚   โ”œโ”€โ”€ routes/
โ”‚   โ”‚   โ””โ”€โ”€ posts.js          โ† exports Express Router
โ”‚   โ””โ”€โ”€ middleware/
โ”‚       โ””โ”€โ”€ auth.js           โ† exports protect middleware function
// โ”€โ”€ How the modules connect โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€

// server/src/controllers/postController.js
const Post = require('../models/Post');  // โ† requires the model

const getAllPosts = async (req, res) => {
  const posts = await Post.find();
  res.json({ success: true, data: posts });
};

module.exports = { getAllPosts };

// server/src/routes/posts.js
const express    = require('express');
const router     = express.Router();
const { getAllPosts } = require('../controllers/postController'); // โ† requires controller

router.get('/', getAllPosts);
module.exports = router;   // โ† exports the router

// server/index.js
const express  = require('express');
const app      = express();
app.use('/api/posts', require('./src/routes/posts')); // โ† requires the router

Core Modules You Will Use in MERN

Module require() call Used For in MERN
path require('path') Building file paths for uploads, static files
fs / fs/promises require('fs/promises') Reading config, writing logs, file operations
crypto require('crypto') Generating random tokens for password resets, email verify
os require('os') Getting temp directory for file uploads
http require('http') Creating HTTP server (Express wraps this)
events require('events') Custom event emitters โ€” used internally by many packages

Common Mistakes

Mistake 1 โ€” Wrong path in require()

โŒ Wrong โ€” forgetting the leading dot on a local file path:

const connectDB = require('src/config/db');
// Error: Cannot find module 'src/config/db'
// Node thinks it is an npm package, not a local file

โœ… Correct โ€” always use ./ or ../ for local files:

const connectDB = require('./src/config/db');   // relative to current file โœ“

Mistake 2 โ€” Circular dependencies

โŒ Wrong โ€” file A requires file B, and file B requires file A:

userController.js โ†’ requires โ†’ postModel.js
postModel.js      โ†’ requires โ†’ userController.js   โ† circular!
// Node.js will partially load one of them โ€” hard-to-debug undefined errors

โœ… Correct โ€” restructure so data flows in one direction. Models should never require controllers. Controllers require models โ€” not the other way around.

Mistake 3 โ€” Modifying module.exports after assignment

โŒ Wrong โ€” assigning to module.exports then trying to add properties to exports:

module.exports = connectDB;   // replaces exports with a function
exports.helper = someHelper;  // ignored โ€” exports no longer === module.exports

โœ… Correct โ€” pick one style per file. Either assign to module.exports directly, or add all properties to the exports shorthand โ€” never mix them:

module.exports = { connectDB, helper: someHelper };  // clean single assignment โœ“

Quick Reference

Task Code
Export one thing module.exports = myFunction
Export multiple things module.exports = { fn1, fn2, CONSTANT }
Import local file const x = require('./path/file')
Import core module const fs = require('fs/promises')
Import npm package const express = require('express')
Destructure import const { fn1, fn2 } = require('./helpers')

🧠 Test Yourself

You call require('config/db') from your Express index.js but get Error: Cannot find module 'config/db'. The file exists at server/src/config/db.js. What is wrong?