JavaScript and TypeScript course Β· Module 5: Advanced JavaScript

CommonJS (require/module.exports)

14 min read
In this lesson11

In the previous exercise we learned about ES Modules - JavaScript's modern module system. Now we'll dive into another, older but still very important module system: CommonJS. Although our Jurassic Park needs the latest technologies to monitor dinosaurs in the browser, its solid server-side infrastructure is built on Node.js, where CommonJS dominates.

What is CommonJS?

CommonJS is a module system created primarily with server-side environments in mind, and it has become the standard in the Node.js ecosystem. It provides a way to organize JavaScript code into separate modules, similar to ES Modules, but with different syntax and behavior.

Unlike ES Modules, which were added to JavaScript in ES6 (2015), CommonJS existed earlier and became the de facto standard for Node.js. Even now, despite official ES Modules support in Node.js, CommonJS remains the dominant format for Node.js modules and in the npm ecosystem.

CommonJS Basics

The main CommonJS concepts are:

  1. The require() function for importing modules
  2. The module.exports or exports object for exporting elements

Let's see how these work in the context of our Jurassic Park system.

Exporting Elements in CommonJS

1. Exporting by Assignment to module.exports

The simplest way to export is by assigning a value to module.exports:

1// dinosaur.js - Dinosaur model
2class Dinosaur {
3  constructor(name, species, diet, dangerLevel) {
4    this.name = name;
5    this.species = species;
6    this.diet = diet;
7    this.dangerLevel = dangerLevel;
8    this.health = 100;
9    this.isContained = true;
10  }
11
12  feed() {
13    this.health = Math.min(100, this.health + 20);
14    return `${this.name} has been fed. Health level: ${this.health}%`;
15  }
16
17  checkStatus() {
18    return {
19      name: this.name,
20      health: this.health,
21      isContained: this.isContained,
22      warning: this.dangerLevel > 7 ? "HIGH DANGER LEVEL" : ""
23    };
24  }
25}
26
27// Export the entire class
28module.exports = Dinosaur;

2. Exporting an Object with Multiple Elements

We can also export multiple elements by assigning an object to module.exports:

1// securityUtils.js - Security utility functions
2const SECURITY_LEVELS = {
3  GREEN: "normal",
4  YELLOW: "elevated",
5  ORANGE: "high",
6  RED: "critical"
7};
8
9function checkFenceStatus(sectorId) {
10  // Simulate checking fence status in a given sector
11  const status = Math.random() > 0.1 ? "active" : "failure";
12  return {
13    sector: sectorId,
14    status,
15    timestamp: new Date().toISOString()
16  };
17}
18
19function triggerAlarm(sector, reason) {
20  console.log(`ALARM! Sector ${sector}: ${reason}`);
21  // Alarm activation logic
22  return {
23    success: true,
24    alarmId: "ALR-" + Math.floor(Math.random() * 10000)
25  };
26}
27
28function getSecurityStatus() {
29  // Returns overall park security status
30  return {
31    level: SECURITY_LEVELS.GREEN,
32    activeAlarms: 0,
33    lastChecked: new Date().toISOString()
34  };
35}
36
37// Export multiple elements as an object
38module.exports = {
39  SECURITY_LEVELS,
40  checkFenceStatus,
41  triggerAlarm,
42  getSecurityStatus
43};

3. Incrementally Adding to exports

We can also add exports one by one using the exports object (which is a reference to module.exports):

1// parkStats.js - Jurassic Park statistics
2let totalVisitors = 0;
3let revenue = 0;
4const incidents = [];
5
6// Export first function
7exports.addVisitors = function(count) {
8  totalVisitors += count;
9  return totalVisitors;
10};
11
12// Export second function
13exports.addRevenue = function(amount) {
14  revenue += amount;
15  return revenue;
16};
17
18// Export third function
19exports.recordIncident = function(type, location, description) {
20  const incident = {
21    id: incidents.length + 1,
22    type,
23    location,
24    description,
25    timestamp: new Date().toISOString()
26  };
27  incidents.push(incident);
28  return incident;
29};
30
31// Export fourth function
32exports.getDailyStats = function() {
33  return {
34    totalVisitors,
35    revenue,
36    incidentCount: incidents.length
37  };
38};

NOTE: Remember that exports is just a reference to module.exports. If you assign something directly to module.exports, you lose that reference, so you cannot mix these styles in one file:

1// This will NOT work
2exports.someFunction = function() { /* ... */ };
3module.exports = SomeClass; // This overwrites the previous export
4
5// This will work
6exports.someFunction = function() { /* ... */ };
7exports.SomeClass = SomeClass;
8
9// This will work too
10module.exports.someFunction = function() { /* ... */ };
11module.exports.SomeClass = SomeClass;
12
13// And this will work (assigning the whole object)
14module.exports = {
15  someFunction: function() { /* ... */ },
16  SomeClass: SomeClass
17};

Importing Modules in CommonJS

1. Importing an Entire Module

1// app.js
2const Dinosaur = require('./dinosaur.js');
3
4// Create new dinosaur instances
5const trex = new Dinosaur("Rex", "Tyrannosaurus", "carnivore", 9);
6const brachi = new Dinosaur("Longneck", "Brachiosaurus", "herbivore", 4);
7
8console.log(trex.checkStatus());
9console.log(brachi.feed());

2. Importing and Destructuring

1// securityMonitor.js
2const security = require('./securityUtils.js');
3
4// Use imported functions and constants from the object
5function monitorSecurity() {
6  const sectors = ["A1", "B2", "C3", "D4"];
7
8  // Check each sector
9  sectors.forEach(sector => {
10    const fenceStatus = security.checkFenceStatus(sector);
11
12    if (fenceStatus.status === "failure") {
13      const alarmResult = security.triggerAlarm(
14        sector,
15        "Electric fence failure!"
16      );
17      console.log(`Alarm ${alarmResult.alarmId} triggered in sector ${sector}`);
18
19      // Update security status
20      console.log("Changing security level to:", security.SECURITY_LEVELS.RED);
21    }
22  });
23}
24
25setInterval(monitorSecurity, 60000); // Check every minute

3. Destructuring on Import

In newer Node.js versions we can also use destructuring directly on import:

1// dashboard.js
2const { checkFenceStatus, triggerAlarm, SECURITY_LEVELS } = require('./securityUtils.js');
3
4// Now we can use functions directly
5function checkAllSectors() {
6  ["A1", "B2", "C3", "D4"].forEach(sector => {
7    console.log(`Checking sector ${sector}: `, checkFenceStatus(sector));
8
9    // Use the imported constant
10    if (Math.random() < 0.1) {
11      console.log(`Setting security level: ${SECURITY_LEVELS.ORANGE}`);
12    }
13  });
14}

Differences Between CommonJS and ES Modules

It's important to understand the key differences between CommonJS and ES Modules:

FeatureCommonJSES Modules
Import syntaxrequire()import ... from ...
Export syntaxmodule.exports = ... or exports.x = ...export ... or export default ...
LoadingSynchronousAsynchronous (loaded before execution)
ExecutionCode executed on each importCode executed only once, then module is cached
Static analysisNot supported (dynamic paths)Supported (paths are static)
Browser supportNot natively supportedSupported in modern browsers
HoistingImports are not hoistedImports are hoisted (usable before declaration)
Tree-shakingHarder to implementEasier due to static analysis

Node.js Project Structure (Jurassic Park Backend)

Let's look at how we might organize the backend of our Jurassic Park management system using Node.js and CommonJS:

1jurassic-park-backend/
2β”œβ”€β”€ config/
3β”‚   β”œβ”€β”€ database.js     # Database configuration
4β”‚   β”œβ”€β”€ environment.js  # Environment variables
5β”‚   └── security.js     # Security settings
6β”œβ”€β”€ models/
7β”‚   β”œβ”€β”€ dinosaur.js     # Dinosaur model
8β”‚   β”œβ”€β”€ employee.js     # Employee model
9β”‚   β”œβ”€β”€ facility.js     # Facility model
10β”‚   └── index.js        # Model aggregator
11β”œβ”€β”€ controllers/
12β”‚   β”œβ”€β”€ dinosaurController.js  # Dinosaur controller
13β”‚   β”œβ”€β”€ securityController.js  # Security controller
14β”‚   └── employeeController.js  # Employee controller
15β”œβ”€β”€ routes/
16β”‚   β”œβ”€β”€ api.js          # Main API router
17β”‚   β”œβ”€β”€ dinosaurs.js    # Dinosaur-related routes
18β”‚   └── security.js     # Security-related routes
19β”œβ”€β”€ services/
20β”‚   β”œβ”€β”€ monitoringService.js   # Monitoring service
21β”‚   β”œβ”€β”€ notificationService.js # Notification service
22β”‚   └── loggingService.js      # Logging service
23β”œβ”€β”€ utils/
24β”‚   β”œβ”€β”€ logger.js       # Logging utility
25β”‚   └── validators.js   # Data validators
26β”œβ”€β”€ middleware/
27β”‚   β”œβ”€β”€ auth.js         # Authentication middleware
28β”‚   └── errorHandler.js # Error handling
29β”œβ”€β”€ app.js              # Main application
30└── server.js           # Server startup file

Let's look at example implementations of these modules:

Configuration File (config/database.js)

1// config/database.js
2module.exports = {
3  host: process.env.DB_HOST || 'localhost',
4  port: process.env.DB_PORT || 27017,
5  name: process.env.DB_NAME || 'jurassic_park',
6  user: process.env.DB_USER || 'admin',
7  password: process.env.DB_PASSWORD || 'secret',
8
9  // Helper function to build the connection URI
10  getConnectionString: function() {
11    return `mongodb://${this.user}:${this.password}@${this.host}:${this.port}/${this.name}`;
12  }
13};

Model (models/dinosaur.js)

1// models/dinosaur.js
2const mongoose = require('mongoose');
3const Schema = mongoose.Schema;
4
5// Dinosaur schema
6const dinosaurSchema = new Schema({
7  name: { type: String, required: true },
8  species: { type: String, required: true },
9  diet: { type: String, enum: ['carnivore', 'herbivore', 'omnivore'], required: true },
10  dangerLevel: { type: Number, min: 1, max: 10, required: true },
11  enclosureId: { type: Schema.Types.ObjectId, ref: 'Enclosure' },
12  health: { type: Number, default: 100 },
13  status: {
14    isContained: { type: Boolean, default: true },
15    lastCheck: { type: Date, default: Date.now }
16  },
17  createdAt: { type: Date, default: Date.now },
18  updatedAt: { type: Date, default: Date.now }
19});
20
21// Schema methods
22dinosaurSchema.methods.feed = function() {
23  this.health = Math.min(100, this.health + 20);
24  this.updatedAt = Date.now();
25  return this.save();
26};
27
28dinosaurSchema.methods.checkStatus = function() {
29  return {
30    name: this.name,
31    health: this.health,
32    isContained: this.status.isContained,
33    warning: this.dangerLevel > 7 ? "HIGH DANGER LEVEL" : ""
34  };
35};
36
37// Static methods
38dinosaurSchema.statics.findDangerous = function() {
39  return this.find({ dangerLevel: { $gte: 8 } });
40};
41
42// Pre-save hook
43dinosaurSchema.pre('save', function(next) {
44  this.updatedAt = Date.now();
45  next();
46});
47
48// Create the model
49const Dinosaur = mongoose.model('Dinosaur', dinosaurSchema);
50
51// Export the model
52module.exports = Dinosaur;

Controller (controllers/dinosaurController.js)

1// controllers/dinosaurController.js
2const Dinosaur = require('../models/dinosaur');
3const logger = require('../utils/logger');
4
5// Controller object with multiple methods
6const dinosaurController = {
7  // Get all dinosaurs
8  getAllDinosaurs: async (req, res) => {
9    try {
10      const dinosaurs = await Dinosaur.find().populate('enclosureId');
11      res.json(dinosaurs);
12    } catch (error) {
13      logger.error('Error fetching dinosaurs:', error);
14      res.status(500).json({ error: 'Server error' });
15    }
16  },
17
18  // Get dinosaur by ID
19  getDinosaurById: async (req, res) => {
20    try {
21      const dinosaur = await Dinosaur.findById(req.params.id).populate('enclosureId');
22
23      if (!dinosaur) {
24        return res.status(404).json({ error: 'Dinosaur not found' });
25      }
26
27      res.json(dinosaur);
28    } catch (error) {
29      logger.error(`Error fetching dinosaur ID=${req.params.id}: `, error);
30      res.status(500).json({ error: 'Server error' });
31    }
32  },
33
34  // Add a new dinosaur
35  createDinosaur: async (req, res) => {
36    try {
37      const { name, species, diet, dangerLevel, enclosureId } = req.body;
38
39      const newDinosaur = new Dinosaur({
40        name,
41        species,
42        diet,
43        dangerLevel,
44        enclosureId
45      });
46
47      await newDinosaur.save();
48      logger.info(`Added new dinosaur: ${name} (${species})`);
49
50      res.status(201).json(newDinosaur);
51    } catch (error) {
52      logger.error('Error creating dinosaur:', error);
53      res.status(400).json({ error: error.message });
54    }
55  },
56
57  // Feed a dinosaur
58  feedDinosaur: async (req, res) => {
59    try {
60      const dinosaur = await Dinosaur.findById(req.params.id);
61
62      if (!dinosaur) {
63        return res.status(404).json({ error: 'Dinosaur not found' });
64      }
65
66      await dinosaur.feed();
67      logger.info(`Fed dinosaur: ${dinosaur.name}`);
68
69      res.json({
70        message: `${dinosaur.name} has been fed. Health level: ${dinosaur.health}%`,
71        health: dinosaur.health
72      });
73    } catch (error) {
74      logger.error(`Error feeding dinosaur ID=${req.params.id}: `, error);
75      res.status(500).json({ error: 'Server error' });
76    }
77  },
78
79  // Find dangerous dinosaurs
80  getDangerousDinosaurs: async (req, res) => {
81    try {
82      const dangerousDinos = await Dinosaur.findDangerous();
83      res.json(dangerousDinos);
84    } catch (error) {
85      logger.error('Error fetching dangerous dinosaurs:', error);
86      res.status(500).json({ error: 'Server error' });
87    }
88  }
89};
90
91// Export the entire controller object
92module.exports = dinosaurController;

Routes (routes/dinosaurs.js)

1// routes/dinosaurs.js
2const express = require('express');
3const router = express.Router();
4const dinosaurController = require('../controllers/dinosaurController');
5const authMiddleware = require('../middleware/auth');
6
7// Dinosaur-related routes
8router.get('/', dinosaurController.getAllDinosaurs);
9router.get('/dangerous', dinosaurController.getDangerousDinosaurs);
10router.get('/:id', dinosaurController.getDinosaurById);
11
12// Routes requiring authentication
13router.post('/', authMiddleware.verifyToken, dinosaurController.createDinosaur);
14router.post('/:id/feed', authMiddleware.verifyToken, dinosaurController.feedDinosaur);
15
16// Export the router
17module.exports = router;

Main Application File (app.js)

1// app.js
2const express = require('express');
3const mongoose = require('mongoose');
4const cors = require('cors');
5const morgan = require('morgan');
6const helmet = require('helmet');
7
8// Config and middleware imports
9const dbConfig = require('./config/database');
10const errorHandler = require('./middleware/errorHandler');
11const logger = require('./utils/logger');
12
13// Route imports
14const apiRouter = require('./routes/api');
15const dinosaurRoutes = require('./routes/dinosaurs');
16const securityRoutes = require('./routes/security');
17
18// Initialize Express app
19const app = express();
20
21// Middleware
22app.use(helmet()); // Security
23app.use(cors()); // Cross-Origin Resource Sharing
24app.use(morgan('dev')); // HTTP request logging
25app.use(express.json()); // JSON parsing
26app.use(express.urlencoded({ extended: false })); // Form parsing
27
28// Connect to database
29mongoose.connect(dbConfig.getConnectionString(), {
30  useNewUrlParser: true,
31  useUnifiedTopology: true
32})
33.then(() => {
34  logger.info('Connected to MongoDB database');
35})
36.catch(err => {
37  logger.error('Database connection error:', err);
38  process.exit(1);
39});
40
41// Routes
42app.use('/api', apiRouter);
43app.use('/api/dinosaurs', dinosaurRoutes);
44app.use('/api/security', securityRoutes);
45
46// Handle 404 errors
47app.use((req, res, next) => {
48  res.status(404).json({ error: 'Not found' });
49});
50
51// Centralized error handling
52app.use(errorHandler);
53
54// Export the application
55module.exports = app;

Server Startup File (server.js)

1// server.js
2const http = require('http');
3const app = require('./app');
4const logger = require('./utils/logger');
5
6// Port settings
7const port = process.env.PORT || 3000;
8app.set('port', port);
9
10// Create HTTP server
11const server = http.createServer(app);
12
13// Listen for connections
14server.listen(port, () => {
15  logger.info(`Jurassic Park server started on port ${port}`);
16});
17
18// Handle server errors
19server.on('error', (error) => {
20  if (error.syscall !== 'listen') {
21    throw error;
22  }
23
24  logger.error(`Server error: ${error}`);
25  process.exit(1);
26});

Circular Dependencies

One challenge when working with CommonJS (and modules in general) is circular dependencies, which occur when module A imports module B and module B imports module A:

1// dinosaurUtils.js
2const Dinosaur = require('./dinosaur');
3
4function isDangerous(dino) {
5  return dino.dangerLevel > 7;
6}
7
8module.exports = { isDangerous };
9
10// dinosaur.js
11const dinosaurUtils = require('./dinosaurUtils'); // Circular dependency!
12
13class Dinosaur {
14  // ... class implementation
15
16  isHighRisk() {
17    return dinosaurUtils.isDangerous(this);
18  }
19}
20
21module.exports = Dinosaur;

CommonJS handles circular dependencies, but it can lead to strange behavior like incomplete objects. To avoid such problems:

  1. Restructure the code to avoid circular dependencies
  2. Use the dependency injection pattern
  3. Move shared code to a third module that both import from

Transpiling CommonJS to ES Modules and Back

In many projects we need to handle both CommonJS and ES Modules. For example, the frontend of our Jurassic Park might use ES Modules while the backend uses CommonJS. What do we do when we want to share code?

Babel for Transpilation

We can use Babel to transpile between formats:

1// babel.config.js for ES Modules -> CommonJS
2module.exports = {
3  presets: [
4    ['@babel/preset-env', {
5      targets: {
6        node: 'current'
7      },
8      modules: 'commonjs' // Convert import/export to require/exports
9    }]
10  ]
11};

Dual Package Hazard

Another approach is to create packages that support both module formats. In package.json you can define:

1{
2  "name": "jurassic-park-utils",
3  "version": "1.0.0",
4  "main": "dist/index.js",
5  "module": "dist/index.mjs",
6  "exports": {
7    "import": "./dist/index.mjs",
8    "require": "./dist/index.js"
9  }
10}

Modern Approach: Node.js with ES Modules

Newer versions of Node.js (from 13.2.0) officially support ES Modules. We can enable this support in a few ways:

  1. Using the .mjs extension for ES Module files
  2. Setting "type": "module" in package.json

However, many npm packages still use CommonJS, so you'll likely need to work with both systems.

Practical Example: Migrating from CommonJS to ES Modules

Let's say we want to migrate part of our backend from CommonJS to ES Modules. Here's how such a migration might look:

Original CommonJS Version (dinosaurController.js)

1// controllers/dinosaurController.js (CommonJS)
2const Dinosaur = require('../models/dinosaur');
3const logger = require('../utils/logger');
4
5function getAllDinosaurs(req, res) {
6  // implementation
7}
8
9function getDinosaurById(req, res) {
10  // implementation
11}
12
13module.exports = {
14  getAllDinosaurs,
15  getDinosaurById
16};

Migration to ES Modules (dinosaurController.mjs)

1// controllers/dinosaurController.mjs (ES Modules)
2import Dinosaur from '../models/dinosaur.mjs';
3import logger from '../utils/logger.mjs';
4
5export function getAllDinosaurs(req, res) {
6  // implementation
7}
8
9export function getDinosaurById(req, res) {
10  // implementation
11}
12
13// We can also export a default object with functions
14export default {
15  getAllDinosaurs,
16  getDinosaurById
17};

Compatibility Wrapper

Sometimes we need to create a wrapper that allows importing an ES Module from CommonJS code:

1// controllers/dinosaurControllerWrapper.js (CommonJS)
2// This file loads an ES Module and exports it as CommonJS
3const importESM = async () => {
4  const controller = await import('./dinosaurController.mjs');
5  return controller.default; // or selected functions
6};
7
8// Export a function that returns a Promise with the controller
9module.exports = importESM();

Summary

CommonJS is a widely used module system in Node.js that allows organizing code into separate modules, which is critical for building scalable applications like the backend of our Jurassic Park management system.

The main CommonJS concepts are:

  • The require() function for importing modules
  • The module.exports or exports object for exporting elements
  • Synchronous module loading
  • Essential module caching for performance

Despite the growing popularity of ES Modules, CommonJS remains an important module system, especially in the Node.js ecosystem. Understanding the differences between CommonJS and ES Modules is key to effectively developing JavaScript applications that can run both in the browser and on the server.

In our Jurassic Park, we use CommonJS for the backend (monitoring, database, security control) and ES Modules for the frontend (user interface, dashboards, notifications). This combination gives us the best of both worlds and lets us efficiently manage a system that must be reliable - after all, the safety of the park and its guests depends on our code!

In the next exercise we'll look at dynamic module importing, which provides even greater flexibility in loading code on demand, which can be critical in emergency situations at Jurassic Park.

Code for this lesson: main.js
1// CommonJS in Jurassic Park - Node.js backend
2// Demo of the require/module.exports system
3
4console.log("Jurassic Park backend - CommonJS");
5console.log("Node.js server management system\n");
6
7// ===========================================
8// CommonJS module simulation
9// ===========================================
10
11// === dinosaur.js - Dinosaur model (CommonJS) ===
12class Dinosaur {
13  constructor(name, species, diet, dangerLevel) {
14    this.name = name;
15    this.species = species;
16    this.diet = diet;
17    this.dangerLevel = dangerLevel;
18    this.health = 100;
19    this.id = `DINO-${Math.random().toString(36).substr(2, 9).toUpperCase()}`;
20  }
21
22  feed() {
23    this.health = Math.min(100, this.health + 20);
24    return { success: true, message: `${this.name} fed`, health: this.health };
25  }
26
27  getDetails() {
28    return {
29      id: this.id,
30      name: this.name,
31      species: this.species,
32      diet: this.diet,
33      dangerLevel: this.dangerLevel,
34      health: this.health
35    };
36  }
37}
38
39// CommonJS export - main class
40const DinosaurModule = Dinosaur;
41
42// === database.js - Database simulation (CommonJS) ===
43const database = {
44  dinosaurs: new Map(),
45  enclosures: new Map(),
46  logs: []
47};
48
49function saveDinosaur(dinosaur) {
50  database.dinosaurs.set(dinosaur.id, dinosaur);
51  database.logs.push({ action: 'SAVE_DINOSAUR', id: dinosaur.id, timestamp: Date.now() });
52  return { success: true, id: dinosaur.id };
53}
54
55function getDinosaur(id) {
56  const dino = database.dinosaurs.get(id);
57  return dino || null;
58}
59
60function getAllDinosaurs() {
61  return Array.from(database.dinosaurs.values());
62}
63
64function deleteDinosaur(id) {
65  const deleted = database.dinosaurs.delete(id);
66  if (deleted) {
67    database.logs.push({ action: 'DELETE_DINOSAUR', id, timestamp: Date.now() });
68  }
69  return { success: deleted };
70}
71
72function getDatabaseStats() {
73  return {
74    dinosaurCount: database.dinosaurs.size,
75    enclosureCount: database.enclosures.size,
76    totalLogs: database.logs.length
77  };
78}
79
80// CommonJS multiple exports
81const DatabaseModule = {
82  saveDinosaur,
83  getDinosaur,
84  getAllDinosaurs,
85  deleteDinosaur,
86  getDatabaseStats
87};
88
89// === config.js - Configuration (CommonJS) ===
90const config = {
91  server: {
92    port: 3000,
93    host: 'localhost',
94    environment: 'development'
95  },
96  database: {
97    url: 'mongodb://localhost:27017/jurassic-park',
98    maxConnections: 10
99  },
100  security: {
101    maxDangerLevel: 10,
102    alertThreshold: 7
103  },
104  park: {
105    maxDinosaurs: 50,
106    maxVisitors: 1000,
107    openingHours: { start: 9, end: 18 }
108  }
109};
110
111// CommonJS config export
112const ConfigModule = config;
113
114// === logger.js - Logging system (CommonJS) ===
115const LOG_LEVELS = {
116  INFO: 'INFO',
117  WARN: 'WARN',
118  ERROR: 'ERROR',
119  DEBUG: 'DEBUG'
120};
121
122function formatLogMessage(level, message) {
123  const timestamp = new Date().toISOString();
124  return `[${timestamp}] [${level}] ${message}`;
125}
126
127function log(level, message) {
128  const formatted = formatLogMessage(level, message);
129  console.log(formatted);
130  database.logs.push({ level, message, timestamp: Date.now() });
131}
132
133function info(message) { log(LOG_LEVELS.INFO, message); }
134function warn(message) { log(LOG_LEVELS.WARN, message); }
135function error(message) { log(LOG_LEVELS.ERROR, message); }
136function debug(message) { log(LOG_LEVELS.DEBUG, message); }
137
138// CommonJS logger exports
139const LoggerModule = {
140  LOG_LEVELS,
141  log,
142  info,
143  warn,
144  error,
145  debug
146};
147
148// === api-routes.js - API endpoints (CommonJS) ===
149class DinosaurAPI {
150  static createDinosaur(req) {
151    const { name, species, diet, dangerLevel } = req.body;
152
153    LoggerModule.info(`Creating dinosaur: ${name}`);
154
155    const dinosaur = new DinosaurModule(name, species, diet, dangerLevel);
156    DatabaseModule.saveDinosaur(dinosaur);
157
158    LoggerModule.info(`Dinosaur created: ${dinosaur.id}`);
159
160    return {
161      status: 201,
162      data: dinosaur.getDetails()
163    };
164  }
165
166  static getDinosaur(req) {
167    const { id } = req.params;
168    LoggerModule.debug(`Fetching dinosaur: ${id}`);
169
170    const dinosaur = DatabaseModule.getDinosaur(id);
171
172    if (!dinosaur) {
173      LoggerModule.warn(`Dinosaur not found: ${id}`);
174      return {
175        status: 404,
176        error: 'Dinosaur not found'
177      };
178    }
179
180    return {
181      status: 200,
182      data: dinosaur.getDetails()
183    };
184  }
185
186  static listDinosaurs() {
187    LoggerModule.debug('Listing all dinosaurs');
188
189    const dinosaurs = DatabaseModule.getAllDinosaurs();
190
191    return {
192      status: 200,
193      data: dinosaurs.map(d => d.getDetails()),
194      count: dinosaurs.length
195    };
196  }
197
198  static deleteDinosaur(req) {
199    const { id } = req.params;
200    LoggerModule.info(`Deleting dinosaur: ${id}`);
201
202    const result = DatabaseModule.deleteDinosaur(id);
203
204    if (!result.success) {
205      return {
206        status: 404,
207        error: 'Dinosaur not found'
208      };
209    }
210
211    LoggerModule.info(`Dinosaur deleted: ${id}`);
212
213    return {
214      status: 200,
215      data: { deleted: true }
216    };
217  }
218
219  static getServerStats() {
220    LoggerModule.debug('Fetching server stats');
221
222    const dbStats = DatabaseModule.getDatabaseStats();
223
224    return {
225      status: 200,
226      data: {
227        ...dbStats,
228        config: {
229          maxDinosaurs: ConfigModule.park.maxDinosaurs,
230          environment: ConfigModule.server.environment
231        }
232      }
233    };
234  }
235}
236
237// CommonJS API export
238const APIModule = DinosaurAPI;
239
240// ===========================================
241// COMMONJS SYSTEM DEMO
242// ===========================================
243console.log("=== SERVER INITIALIZATION ===\n");
244
245LoggerModule.info('Server starting...');
246LoggerModule.info(`Port: ${ConfigModule.server.port}`);
247LoggerModule.info(`Environment: ${ConfigModule.server.environment}`);
248
249console.log("\n=== CREATING DINOSAURS (API CALLS) ===\n");
250
251// API request simulation
252const req1 = {
253  body: { name: 'Rexy', species: 'T-Rex', diet: 'carnivore', dangerLevel: 9 }
254};
255const response1 = APIModule.createDinosaur(req1);
256console.log('POST /api/dinosaurs:', JSON.stringify(response1, null, 2));
257
258const req2 = {
259  body: { name: 'Blue', species: 'Velociraptor', diet: 'carnivore', dangerLevel: 7 }
260};
261const response2 = APIModule.createDinosaur(req2);
262console.log('\nPOST /api/dinosaurs:', JSON.stringify(response2, null, 2));
263
264const req3 = {
265  body: { name: 'Gentle Giant', species: 'Brachiosaurus', diet: 'herbivore', dangerLevel: 2 }
266};
267const response3 = APIModule.createDinosaur(req3);
268console.log('\nPOST /api/dinosaurs:', JSON.stringify(response3, null, 2));
269
270console.log("\n=== FETCHING DATA (API CALLS) ===\n");
271
272// List of all dinosaurs
273const listResponse = APIModule.listDinosaurs();
274console.log('GET /api/dinosaurs:', JSON.stringify(listResponse, null, 2));
275
276console.log("\n=== SERVER STATISTICS ===\n");
277
278const statsResponse = APIModule.getServerStats();
279console.log('GET /api/stats:', JSON.stringify(statsResponse, null, 2));
280
281console.log("\n=== DIFFERENCES: CommonJS vs ES Modules ===\n");
282
283console.log("CommonJS (Node.js):");
284console.log("  - Uses: require() and module.exports");
285console.log("  - Synchronous loading");
286console.log("  - Dynamic imports at runtime");
287console.log("  - Value is copied\n");
288
289console.log("ES Modules (ES6+):");
290console.log("  - Uses: import and export");
291console.log("  - Asynchronous loading");
292console.log("  - Static imports at compile time");
293console.log("  - Value is a reference (live binding)\n");
294
295console.log("CommonJS example:");
296console.log("  // dinosaur.js");
297console.log("  module.exports = class Dinosaur { ... };");
298console.log("");
299console.log("  // main.js");
300console.log("  const Dinosaur = require('./dinosaur');\n");
301
302console.log("ES Modules example:");
303console.log("  // dinosaur.js");
304console.log("  export default class Dinosaur { ... }");
305console.log("");
306console.log("  // main.js");
307console.log("  import Dinosaur from './dinosaur.js';\n");
308
309LoggerModule.info('Demo completed successfully!');
310
311console.log("\nJurassic Park backend ready!");

Spotted a mistake in this lesson?

Hands-on tasks in the game

  • Horizontal ordering

    Arrange the function export elements:

Useful articles