javascript-today

Modules and Require

NodeJS uses a module system to organize code into reusable pieces. Understanding modules is essential for writing maintainable Node applications.

What Are Modules?

A module is simply a JavaScript file that exports functionality for other files to use. NodeJS uses the CommonJS module system by default.

// math.js - a simple module
function add(a, b) {
  return a + b;
}

function multiply(a, b) {
  return a * b;
}

// Export functions
module.exports = { add, multiply };
// app.js - using the module
const math = require('./math.js');

console.log(math.add(5, 3));      // 8
console.log(math.multiply(4, 2)); // 8

The require() Function

require() loads and executes a module, returning whatever that module exports:

// Relative path (your own modules)
const myModule = require('./myModule');
const utils = require('../utils/helpers');

// Core Node modules (built-in)
const fs = require('fs');
const path = require('path');
const http = require('http');

// NPM packages (from node_modules)
const express = require('express');
const lodash = require('lodash');

Path rules:

  • ./ or ../ = relative path to your file
  • No path prefix = core module or npm package
  • .js extension is optional

Exporting from Modules

Method 1: Export Object

// calculator.js
function add(a, b) { return a + b; }
function subtract(a, b) { return a - b; }

module.exports = {
  add,
  subtract
};

Method 2: Export Single Value

// greeter.js
module.exports = function(name) {
  return `Hello, ${name}!`;
};
// app.js
const greet = require('./greeter');
console.log(greet('Alice'));  // "Hello, Alice!"

Method 3: Exports Shorthand

// utils.js
exports.double = (n) => n * 2;
exports.triple = (n) => n * 3;

// Same as:
// module.exports = {
//   double: (n) => n * 2,
//   triple: (n) => n * 3
// };

⚠️ Important: Don’t reassign exports:

// ❌ DON'T DO THIS
exports = { foo: 'bar' };  // Breaks the reference

// ✅ DO THIS INSTEAD
module.exports = { foo: 'bar' };

Core Node Modules

NodeJS comes with built-in modules for common tasks:

// File system operations
const fs = require('fs');
fs.readFileSync('data.txt', 'utf8');

// Path manipulation
const path = require('path');
const fullPath = path.join(__dirname, 'data', 'users.json');

// Operating system info
const os = require('os');
console.log(os.platform());  // 'darwin', 'win32', etc.

// HTTP server
const http = require('http');

// URL parsing
const url = require('url');

// Events
const EventEmitter = require('events');

// Utilities
const util = require('util');

Full list: nodejs.org/api

Module Patterns

Pattern 1: Export Class

// User.js
class User {
  constructor(name, email) {
    this.name = name;
    this.email = email;
  }
  
  greet() {
    return `Hi, I'm ${this.name}`;
  }
}

module.exports = User;
// app.js
const User = require('./User');
const user = new User('Bob', 'bob@example.com');
console.log(user.greet());

Pattern 2: Singleton Pattern

// database.js
class Database {
  constructor() {
    this.connected = false;
  }
  
  connect() {
    this.connected = true;
    console.log('Database connected');
  }
}

// Export single instance
module.exports = new Database();
// app.js
const db = require('./database');
db.connect();  // Same instance everywhere

Pattern 3: Factory Pattern

// logger.js
function createLogger(prefix) {
  return {
    info: (msg) => console.log(`[${prefix}] INFO: ${msg}`),
    error: (msg) => console.error(`[${prefix}] ERROR: ${msg}`)
  };
}

module.exports = createLogger;
// app.js
const createLogger = require('./logger');
const logger = createLogger('APP');
logger.info('Server started');

Module Caching

Modules are cached after first load:

// counter.js
let count = 0;
module.exports = {
  increment: () => ++count,
  getCount: () => count
};
// app.js
const counter1 = require('./counter');
const counter2 = require('./counter');

counter1.increment();
console.log(counter2.getCount());  // 1 (same instance!)

This means:

  • Modules run only once
  • Subsequent requires return cached version
  • State is shared across all requires

Circular Dependencies

Be careful of circular requires:

// a.js
const b = require('./b');
exports.foo = () => console.log('A');

// b.js
const a = require('./a');
exports.bar = () => console.log('B');

This can cause issues. Refactor to avoid circular dependencies.

ES6 Modules (import/export)

Modern NodeJS also supports ES6 modules:

// math.mjs (note the .mjs extension)
export function add(a, b) {
  return a + b;
}

export function multiply(a, b) {
  return a * b;
}
// app.mjs
import { add, multiply } from './math.mjs';
console.log(add(5, 3));

Or in package.json set "type": "module" to use .js extension.

For this tutorial series, we’ll stick with CommonJS (require/module.exports) as it’s still the default and most common.

Summary

Concept Syntax
Import module const mod = require('./module')
Export object module.exports = { a, b }
Export function module.exports = function() {}
Export class module.exports = MyClass
Shorthand exports exports.foo = 'bar'
Core modules require('fs')
NPM packages require('express')