Prerequisites
- ES6 modules and import/export patterns ๐ฆ
- Basic Node.js understanding ๐ฅ๏ธ
- TypeScript compilation basics โก
What you'll learn
- Master CommonJS require() and module.exports patterns ๐ฅ๏ธ
- Configure TypeScript for Node.js compatibility ๐ ๏ธ
- Bridge ES modules and CommonJS seamlessly ๐
- Handle legacy Node.js libraries with confidence โจ
๐ฏ Introduction
Welcome to the world of CommonJS modules in TypeScript! ๐ In this guide, weโll explore how to work with Node.jsโs traditional module system while maintaining TypeScriptโs powerful type safety.
Youโll discover how to bridge the gap between modern ES6 modules and the CommonJS ecosystem that powers millions of Node.js applications. Whether youโre working with legacy codebases ๐๏ธ, integrating third-party libraries ๐, or building robust backend services ๐ฅ๏ธ, mastering CommonJS compatibility is essential for real-world TypeScript development.
By the end of this tutorial, youโll be seamlessly switching between module systems like a true Node.js wizard! ๐งโโ๏ธ Letโs dive in! ๐โโ๏ธ
๐ Understanding CommonJS
๐ค What is CommonJS?
CommonJS is like the original blueprint for JavaScript modules ๐. Think of it as the foundation that Node.js was built on - before ES6 modules existed, CommonJS was the way to organize and share code in JavaScript environments.
In Node.js terms, CommonJS provides:
- โจ Synchronous loading - modules load immediately when required
- ๐ require() function - the classic way to import modules
- ๐ก๏ธ module.exports - the standard way to export functionality
- ๐ฆ Automatic wrapping - each file gets its own scope
๐ก Why Learn CommonJS with TypeScript?
Hereโs why CommonJS knowledge is crucial:
- Legacy Compatibility ๐ง: Millions of Node.js packages use CommonJS
- Enterprise Codebases ๐ข: Many production systems rely on CommonJS
- Library Integration ๐: Many npm packages are still CommonJS-only
- Migration Scenarios ๐: Converting from CommonJS to ES modules
- Universal Libraries ๐: Building packages that work everywhere
Real-world example: When building a Node.js API that uses Express.js ๐, youโll often need to integrate with CommonJS libraries while writing modern TypeScript code!
๐ง Basic CommonJS Patterns
๐ require() and module.exports
Letโs start with the fundamental CommonJS patterns:
// ๐ utils/math.ts - CommonJS exports
// ๐งฎ Traditional CommonJS export patterns
// Pattern 1: Direct assignment to module.exports
module.exports = {
PI: 3.14159,
E: 2.71828,
// โ Basic math operations
add: (a: number, b: number): number => {
return a + b;
},
// โ๏ธ Multiplication with logging
multiply: (a: number, b: number): number => {
console.log(`๐ข Multiplying ${a} ร ${b}`);
return a * b;
}
};
// Pattern 2: Individual property exports
module.exports.subtract = (a: number, b: number): number => {
return a - b;
};
module.exports.divide = (a: number, b: number): number => {
if (b === 0) {
throw new Error("Division by zero! ๐ซ");
}
return a / b;
};
// ๐ services/calculator.ts - CommonJS imports
// ๐ฏ CommonJS require syntax
const math = require('../utils/math');
class Calculator {
// โ Using imported functions
calculate(operation: string, a: number, b: number): number {
switch (operation) {
case 'add':
return math.add(a, b);
case 'multiply':
return math.multiply(a, b);
case 'subtract':
return math.subtract(a, b);
case 'divide':
return math.divide(a, b);
default:
throw new Error(`Unknown operation: ${operation} ๐คทโโ๏ธ`);
}
}
// ๐ต Circle area using imported constants
getCircleArea(radius: number): number {
return math.PI * math.multiply(radius, radius);
}
}
// ๐ Export the calculator class
module.exports = Calculator;
๐ก Explanation: CommonJS uses require()
for imports and module.exports
for exports. Notice how we access properties from the exported object!
๐ฏ TypeScript Configuration for CommonJS
Configure TypeScript to work seamlessly with CommonJS:
// ๐ tsconfig.json - CommonJS configuration
{
"compilerOptions": {
"target": "ES2020", // ๐ฏ Modern JavaScript target
"module": "CommonJS", // ๐ฆ Use CommonJS modules
"moduleResolution": "node", // ๐ Node.js module resolution
"esModuleInterop": true, // ๐ Allow ES/CommonJS interop
"allowSyntheticDefaultImports": true, // โจ Synthetic default imports
"strict": true, // ๐ก๏ธ Maximum type safety
"skipLibCheck": true, // โก Skip lib type checking
"forceConsistentCasingInFileNames": true, // ๐ Consistent file names
"outDir": "./dist", // ๐ Output directory
"rootDir": "./src", // ๐ Source directory
"declaration": true, // ๐ Generate .d.ts files
"declarationMap": true, // ๐บ๏ธ Source maps for declarations
"sourceMap": true // ๐บ๏ธ Source maps for debugging
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
// ๐ package.json - Node.js package configuration
{
"name": "typescript-commonjs-example",
"version": "1.0.0",
"type": "commonjs", // ๐ฆ Explicit CommonJS declaration
"main": "dist/index.js", // ๐ฏ Entry point
"types": "dist/index.d.ts", // ๐ TypeScript definitions
"scripts": {
"build": "tsc", // ๐จ Build TypeScript
"start": "node dist/index.js", // ๐ Start application
"dev": "ts-node src/index.ts", // โก Development mode
"watch": "tsc --watch" // ๐ Watch mode
},
"dependencies": {
"express": "^4.18.2" // ๐ Example dependency
},
"devDependencies": {
"@types/node": "^20.0.0", // ๐ฅ๏ธ Node.js types
"@types/express": "^4.17.17", // ๐ Express types
"typescript": "^5.0.0", // ๐ TypeScript compiler
"ts-node": "^10.9.0" // โก Development runner
}
}
๐ Mixed Module Patterns
Combining different export styles:
// ๐ database/connection.ts - Mixed CommonJS exports
import { Pool, PoolConfig } from 'pg'; // ๐ PostgreSQL types
// ๐๏ธ Database configuration interface
interface DatabaseConfig extends PoolConfig {
retryAttempts?: number;
retryDelay?: number;
}
// ๐ Connection pool class
class DatabaseConnection {
private pool: Pool;
private config: DatabaseConfig;
constructor(config: DatabaseConfig) {
this.config = {
retryAttempts: 3,
retryDelay: 1000,
...config
};
this.pool = new Pool(this.config);
console.log('๐ Database connection pool created');
}
// ๐ Execute query with type safety
async query<T = any>(text: string, params?: any[]): Promise<T[]> {
try {
console.log(`๐ Executing query: ${text.substring(0, 50)}...`);
const result = await this.pool.query(text, params);
return result.rows;
} catch (error) {
console.error('โ Database query failed:', error);
throw error;
}
}
// ๐งน Clean shutdown
async close(): Promise<void> {
await this.pool.end();
console.log('๐ Database connection pool closed');
}
}
// ๐ฆ CommonJS exports - multiple patterns
module.exports = DatabaseConnection; // Default export
module.exports.DatabaseConnection = DatabaseConnection; // Named export
module.exports.createConnection = (config: DatabaseConfig) => {
return new DatabaseConnection(config); // Factory function
};
// ๐ฏ Export types for TypeScript users
export type { DatabaseConfig };
๐ก Practical Examples
๐ฅ๏ธ Example 1: Express.js Server with TypeScript
Letโs build a real-world Express server using CommonJS patterns:
// ๐ server/app.ts - Express application setup
const express = require('express');
const cors = require('cors');
const helmet = require('helmet');
// ๐๏ธ Import our custom modules
const userRoutes = require('./routes/users');
const authMiddleware = require('./middleware/auth');
const errorHandler = require('./middleware/errorHandler');
// ๐ Application configuration interface
interface AppConfig {
port: number;
cors: {
origin: string[];
credentials: boolean;
};
rateLimit: {
windowMs: number;
max: number;
};
}
// ๐ Express application class
class ExpressApp {
private app: any; // Express app instance
private config: AppConfig;
constructor(config: AppConfig) {
this.config = config;
this.app = express();
this.setupMiddleware();
this.setupRoutes();
this.setupErrorHandling();
}
// ๐ ๏ธ Configure middleware
private setupMiddleware(): void {
// ๐ก๏ธ Security middleware
this.app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"]
}
}
}));
// ๐ CORS configuration
this.app.use(cors({
origin: this.config.cors.origin,
credentials: this.config.cors.credentials
}));
// ๐ Request parsing
this.app.use(express.json({ limit: '10mb' }));
this.app.use(express.urlencoded({ extended: true }));
console.log('๐ง Middleware configured successfully');
}
// ๐ฃ๏ธ Setup application routes
private setupRoutes(): void {
// ๐ Health check endpoint
this.app.get('/health', (req: any, res: any) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
uptime: process.uptime(),
emoji: 'โ
'
});
});
// ๐ค User routes with authentication
this.app.use('/api/users', authMiddleware, userRoutes);
// ๐ API information endpoint
this.app.get('/api/info', (req: any, res: any) => {
res.json({
name: 'TypeScript CommonJS API',
version: '1.0.0',
description: 'Example API using CommonJS modules ๐',
endpoints: [
'GET /health',
'GET /api/info',
'GET /api/users',
'POST /api/users'
]
});
});
console.log('๐ฃ๏ธ Routes configured successfully');
}
// ๐จ Error handling setup
private setupErrorHandling(): void {
// 404 handler
this.app.use((req: any, res: any) => {
res.status(404).json({
error: 'Not Found',
message: `Route ${req.method} ${req.path} not found ๐`,
timestamp: new Date().toISOString()
});
});
// Global error handler
this.app.use(errorHandler);
console.log('๐จ Error handling configured');
}
// ๐ Start the server
start(): void {
this.app.listen(this.config.port, () => {
console.log(`๐ Server running on port ${this.config.port}`);
console.log(`๐ Health check: http://localhost:${this.config.port}/health`);
console.log(`๐ API info: http://localhost:${this.config.port}/api/info`);
});
}
// ๐ฑ Get express app instance
getApp(): any {
return this.app;
}
}
// ๐ฆ CommonJS export
module.exports = ExpressApp;
// ๐ routes/users.ts - User routes module
const express = require('express');
const router = express.Router();
// ๐ฏ User interface
interface User {
id: string;
name: string;
email: string;
createdAt: Date;
role: 'admin' | 'user' | 'moderator';
}
// ๐ Mock user database
const users: User[] = [
{
id: '1',
name: 'Alice Johnson',
email: '[email protected]',
createdAt: new Date('2024-01-15'),
role: 'admin'
},
{
id: '2',
name: 'Bob Smith',
email: '[email protected]',
createdAt: new Date('2024-02-20'),
role: 'user'
},
{
id: '3',
name: 'Charlie Brown',
email: '[email protected]',
createdAt: new Date('2024-03-10'),
role: 'moderator'
}
];
// ๐ GET /api/users - List all users
router.get('/', (req: any, res: any) => {
try {
const { role, limit = '10' } = req.query;
let filteredUsers = users;
// ๐ Filter by role if specified
if (role) {
filteredUsers = users.filter(user => user.role === role);
}
// ๐ Apply limit
const limitNum = parseInt(limit as string, 10);
const result = filteredUsers.slice(0, limitNum);
res.json({
users: result,
total: filteredUsers.length,
showing: result.length,
message: `Found ${result.length} users ๐ฅ`
});
console.log(`๐ Retrieved ${result.length} users`);
} catch (error) {
console.error('โ Error fetching users:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// ๐ค GET /api/users/:id - Get specific user
router.get('/:id', (req: any, res: any) => {
try {
const { id } = req.params;
const user = users.find(u => u.id === id);
if (!user) {
return res.status(404).json({
error: 'User not found',
message: `User with ID ${id} does not exist ๐`
});
}
res.json({
user,
message: `User ${user.name} retrieved successfully โ
`
});
console.log(`๐ค Retrieved user: ${user.name}`);
} catch (error) {
console.error('โ Error fetching user:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// โ POST /api/users - Create new user
router.post('/', (req: any, res: any) => {
try {
const { name, email, role = 'user' } = req.body;
// โ
Validation
if (!name || !email) {
return res.status(400).json({
error: 'Validation failed',
message: 'Name and email are required ๐'
});
}
// ๐ Check for duplicate email
const existingUser = users.find(u => u.email === email);
if (existingUser) {
return res.status(409).json({
error: 'Conflict',
message: 'User with this email already exists ๐ง'
});
}
// ๐ฏ Create new user
const newUser: User = {
id: (users.length + 1).toString(),
name,
email,
createdAt: new Date(),
role: role as User['role']
};
users.push(newUser);
res.status(201).json({
user: newUser,
message: `User ${newUser.name} created successfully ๐`
});
console.log(`โ Created user: ${newUser.name}`);
} catch (error) {
console.error('โ Error creating user:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
// ๐ฆ Export the router
module.exports = router;
// ๐ middleware/auth.ts - Authentication middleware
// ๐ Simple authentication middleware example
interface AuthRequest {
headers: {
authorization?: string;
[key: string]: any;
};
user?: {
id: string;
role: string;
};
}
// ๐ก๏ธ Authentication middleware function
const authMiddleware = (req: AuthRequest, res: any, next: any) => {
try {
const authHeader = req.headers.authorization;
if (!authHeader) {
return res.status(401).json({
error: 'Unauthorized',
message: 'No authorization header provided ๐'
});
}
// ๐ฏ Simple token validation (in real app, verify JWT)
const token = authHeader.replace('Bearer ', '');
if (token === 'valid-token-123') {
// โ
Valid token - attach user info
req.user = {
id: 'user123',
role: 'admin'
};
console.log('โ
User authenticated successfully');
next();
} else {
return res.status(401).json({
error: 'Unauthorized',
message: 'Invalid token provided ๐ซ'
});
}
} catch (error) {
console.error('โ Authentication error:', error);
res.status(500).json({ error: 'Internal server error' });
}
};
// ๐ฆ Export the middleware
module.exports = authMiddleware;
๐ฏ Try it yourself: Add user update and delete endpoints, plus role-based access control!
๐ฆ Example 2: npm Package with CommonJS and ES Module Support
Create a dual-mode package that works with both systems:
// ๐ src/index.ts - Main package entry point
// ๐ Universal package that supports both CommonJS and ES modules
// ๐ฏ Core validation functions
export interface ValidationRule {
type: 'required' | 'email' | 'minLength' | 'maxLength' | 'pattern';
value?: any;
message?: string;
}
export interface ValidationResult {
isValid: boolean;
errors: string[];
field?: string;
}
// โ
Email validation
export const validateEmail = (email: string): ValidationResult => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
const isValid = emailRegex.test(email);
return {
isValid,
errors: isValid ? [] : ['Invalid email format ๐ง'],
field: 'email'
};
};
// ๐ Length validation
export const validateLength = (
value: string,
min: number,
max: number
): ValidationResult => {
const length = value.length;
const errors: string[] = [];
if (length < min) {
errors.push(`Must be at least ${min} characters long ๐`);
}
if (length > max) {
errors.push(`Must be no more than ${max} characters long โ๏ธ`);
}
return {
isValid: errors.length === 0,
errors,
field: 'length'
};
};
// ๐ Pattern validation
export const validatePattern = (
value: string,
pattern: RegExp,
message: string = 'Invalid format'
): ValidationResult => {
const isValid = pattern.test(value);
return {
isValid,
errors: isValid ? [] : [`${message} ๐`],
field: 'pattern'
};
};
// ๐ฏ Comprehensive validator class
export class Validator {
private rules: Map<string, ValidationRule[]> = new Map();
// โ Add validation rule
addRule(field: string, rule: ValidationRule): void {
if (!this.rules.has(field)) {
this.rules.set(field, []);
}
this.rules.get(field)!.push(rule);
}
// โ
Validate all fields
validate(data: Record<string, any>): Record<string, ValidationResult> {
const results: Record<string, ValidationResult> = {};
for (const [field, rules] of this.rules) {
const value = data[field];
const errors: string[] = [];
for (const rule of rules) {
switch (rule.type) {
case 'required':
if (!value || (typeof value === 'string' && value.trim() === '')) {
errors.push(rule.message || `${field} is required ๐`);
}
break;
case 'email':
if (value && !validateEmail(value).isValid) {
errors.push(rule.message || 'Invalid email format ๐ง');
}
break;
case 'minLength':
if (value && value.length < rule.value) {
errors.push(rule.message || `Must be at least ${rule.value} characters ๐`);
}
break;
case 'maxLength':
if (value && value.length > rule.value) {
errors.push(rule.message || `Must be no more than ${rule.value} characters โ๏ธ`);
}
break;
case 'pattern':
if (value && !rule.value.test(value)) {
errors.push(rule.message || 'Invalid format ๐');
}
break;
}
}
results[field] = {
isValid: errors.length === 0,
errors,
field
};
}
return results;
}
// ๐ Check if all validations pass
isValid(data: Record<string, any>): boolean {
const results = this.validate(data);
return Object.values(results).every(result => result.isValid);
}
}
// ๐ฏ Convenient factory function
export const createValidator = (): Validator => {
return new Validator();
};
// ๐ฆ Default export for CommonJS compatibility
const validatorPackage = {
validateEmail,
validateLength,
validatePattern,
Validator,
createValidator
};
// ๐ Dual export support
export default validatorPackage;
// CommonJS compatibility
if (typeof module !== 'undefined' && module.exports) {
module.exports = validatorPackage;
module.exports.default = validatorPackage;
module.exports.validateEmail = validateEmail;
module.exports.validateLength = validateLength;
module.exports.validatePattern = validatePattern;
module.exports.Validator = Validator;
module.exports.createValidator = createValidator;
}
// ๐ package.json - Dual module support configuration
{
"name": "typescript-validator-universal",
"version": "1.0.0",
"description": "Universal validation library supporting both CommonJS and ES modules ๐",
"main": "dist/cjs/index.js", // ๐ฆ CommonJS entry point
"module": "dist/esm/index.js", // ๐ฆ ES Module entry point
"types": "dist/types/index.d.ts", // ๐ TypeScript definitions
"exports": {
".": {
"require": "./dist/cjs/index.js", // ๐ฏ CommonJS
"import": "./dist/esm/index.js", // ๐ฏ ES Module
"types": "./dist/types/index.d.ts" // ๐ Types
}
},
"files": [
"dist/",
"README.md"
],
"scripts": {
"build": "npm run build:cjs && npm run build:esm && npm run build:types",
"build:cjs": "tsc -p tsconfig.cjs.json",
"build:esm": "tsc -p tsconfig.esm.json",
"build:types": "tsc -p tsconfig.types.json",
"test": "jest",
"lint": "eslint src/**/*.ts"
},
"keywords": ["typescript", "validation", "commonjs", "esm", "universal"],
"author": "Your Name",
"license": "MIT"
}
๐ Advanced CommonJS Patterns
๐งโโ๏ธ Dynamic Requires and Conditional Loading
Advanced patterns for dynamic module loading:
// ๐ utils/module-loader.ts - Dynamic module loading utilities
interface ModuleCache {
[key: string]: any;
}
// ๐ฆ Module loader with caching
class DynamicModuleLoader {
private cache: ModuleCache = {};
// ๐ Load module with caching
loadModule<T = any>(modulePath: string): T {
if (this.cache[modulePath]) {
console.log(`๐ฆ Using cached module: ${modulePath}`);
return this.cache[modulePath];
}
try {
console.log(`โณ Loading module: ${modulePath}`);
const module = require(modulePath);
this.cache[modulePath] = module;
console.log(`โ
Module loaded: ${modulePath}`);
return module;
} catch (error) {
console.error(`โ Failed to load module: ${modulePath}`, error);
throw new Error(`Module loading failed: ${modulePath}`);
}
}
// ๐ Conditional loading based on environment
loadEnvironmentModule(basePath: string): any {
const env = process.env.NODE_ENV || 'development';
const modulePath = `${basePath}.${env}`;
try {
return this.loadModule(modulePath);
} catch (error) {
console.log(`โ ๏ธ Environment module not found: ${modulePath}, falling back to base`);
return this.loadModule(basePath);
}
}
// ๐ Check if module exists
moduleExists(modulePath: string): boolean {
try {
require.resolve(modulePath);
return true;
} catch (error) {
return false;
}
}
// ๐งน Clear module cache
clearCache(modulePath?: string): void {
if (modulePath) {
delete this.cache[modulePath];
console.log(`๐งน Cleared cache for: ${modulePath}`);
} else {
this.cache = {};
console.log('๐งน Cleared entire module cache');
}
}
// ๐ Get cache statistics
getCacheStats(): { total: number; modules: string[] } {
const modules = Object.keys(this.cache);
return {
total: modules.length,
modules
};
}
}
// ๐ฏ Plugin system using dynamic loading
class PluginManager {
private plugins: Map<string, any> = new Map();
private loader = new DynamicModuleLoader();
// ๐ Load plugin by name
loadPlugin(name: string, pluginPath?: string): void {
const path = pluginPath || `./plugins/${name}`;
try {
const plugin = this.loader.loadModule(path);
// โ
Validate plugin interface
if (typeof plugin.init !== 'function') {
throw new Error('Plugin must have an init() function');
}
// ๐ Initialize plugin
plugin.init();
this.plugins.set(name, plugin);
console.log(`๐ Plugin loaded: ${name}`);
} catch (error) {
console.error(`โ Failed to load plugin: ${name}`, error);
}
}
// ๐ฎ Execute plugin method
executePlugin(name: string, method: string, ...args: any[]): any {
const plugin = this.plugins.get(name);
if (!plugin) {
throw new Error(`Plugin not found: ${name}`);
}
if (typeof plugin[method] !== 'function') {
throw new Error(`Method ${method} not found in plugin ${name}`);
}
console.log(`๐ฎ Executing ${name}.${method}()`);
return plugin[method](...args);
}
// ๐ List loaded plugins
listPlugins(): string[] {
return Array.from(this.plugins.keys());
}
}
// ๐ฆ Export utilities
module.exports = {
DynamicModuleLoader,
PluginManager,
// ๐ฏ Convenience functions
loadModule: (path: string) => new DynamicModuleLoader().loadModule(path),
createPluginManager: () => new PluginManager()
};
๐๏ธ Module Wrapping and Namespace Creation
Advanced patterns for organizing CommonJS modules:
// ๐ core/namespace.ts - Namespace creation utilities
// ๐๏ธ Namespace builder for organizing modules
class NamespaceBuilder {
private namespace: any = {};
// โ Add module to namespace
addModule(name: string, module: any): this {
const parts = name.split('.');
let current = this.namespace;
// ๐๏ธ Create nested structure
for (let i = 0; i < parts.length - 1; i++) {
const part = parts[i];
if (!current[part]) {
current[part] = {};
}
current = current[part];
}
// ๐ฆ Add the module
current[parts[parts.length - 1]] = module;
console.log(`๐ฆ Added module to namespace: ${name}`);
return this;
}
// ๐ Auto-load modules from directory
autoLoad(basePath: string, pattern: RegExp = /\.js$/): this {
const fs = require('fs');
const path = require('path');
try {
const files = fs.readdirSync(basePath);
for (const file of files) {
if (pattern.test(file)) {
const moduleName = path.basename(file, path.extname(file));
const modulePath = path.join(basePath, file);
try {
const module = require(modulePath);
this.addModule(moduleName, module);
} catch (error) {
console.error(`โ Failed to auto-load: ${modulePath}`, error);
}
}
}
} catch (error) {
console.error(`โ Failed to read directory: ${basePath}`, error);
}
return this;
}
// ๐ฏ Get the built namespace
build(): any {
return this.namespace;
}
// ๐ Get module from namespace
get(path: string): any {
const parts = path.split('.');
let current = this.namespace;
for (const part of parts) {
if (!current[part]) {
return undefined;
}
current = current[part];
}
return current;
}
}
// ๐ฏ Module wrapper for consistent interface
const wrapModule = (module: any, metadata: any = {}) => {
return {
// ๐ Module metadata
_meta: {
name: metadata.name || 'unknown',
version: metadata.version || '1.0.0',
author: metadata.author || 'unknown',
loadedAt: new Date(),
...metadata
},
// ๐ฆ Original module
...module,
// ๐ง Utility methods
getInfo: () => {
return {
name: metadata.name,
version: metadata.version,
exports: Object.keys(module),
loadedAt: new Date()
};
}
};
};
// ๐ฆ Export the namespace utilities
module.exports = {
NamespaceBuilder,
wrapModule,
// ๐ฏ Convenience functions
createNamespace: () => new NamespaceBuilder(),
// ๐๏ธ Quick namespace creation
buildNamespace: (modules: Record<string, any>) => {
const builder = new NamespaceBuilder();
for (const [name, module] of Object.entries(modules)) {
builder.addModule(name, module);
}
return builder.build();
}
};
โ ๏ธ Common Pitfalls and Solutions
๐ฑ Pitfall 1: Mixed Module Systems Confusion
// โ Wrong way - mixing import/require inconsistently
import express from 'express'; // ES6 import
const cors = require('cors'); // CommonJS require
// This creates confusion and potential issues!
// ๐ฅ TypeScript compilation errors
export default function createApp() {
// ...
}
module.exports = createApp; // Mixing export styles!
// โ
Correct way - consistent CommonJS approach
const express = require('express');
const cors = require('cors');
// ๐ฏ Consistent CommonJS exports
function createApp() {
const app = express();
app.use(cors());
app.use(express.json());
return app;
}
// ๐ฆ Single export style
module.exports = createApp;
module.exports.createApp = createApp; // Named export for flexibility
๐คฏ Pitfall 2: TypeScript Configuration Issues
// โ Wrong configuration - causes import issues
// tsconfig.json with problematic settings
{
"compilerOptions": {
"module": "ES2020", // ๐ฅ Conflicts with CommonJS usage
"moduleResolution": "classic", // ๐ฅ Wrong resolution strategy
"esModuleInterop": false // ๐ฅ Breaks interop
}
}
// โ
Correct configuration for CommonJS
{
"compilerOptions": {
"module": "CommonJS", // ๐ฏ Consistent with runtime
"moduleResolution": "node", // โ
Node.js resolution
"esModuleInterop": true, // ๐ Enable ES/CommonJS interop
"allowSyntheticDefaultImports": true, // โ
Synthetic defaults
"target": "ES2020", // ๐ฏ Modern JS features
"strict": true, // ๐ก๏ธ Type safety
"skipLibCheck": true // โก Performance
}
}
๐ฅ Pitfall 3: require() vs import Type Confusion
// โ Wrong way - losing type information
const someLibrary = require('some-library'); // ๐ฅ No type checking!
// someLibrary. <-- No autocomplete, no type safety
// Using any types everywhere
const result: any = someLibrary.doSomething();
// โ
Correct way - maintaining types with CommonJS
// Option 1: Import types separately
import type { SomeLibraryType } from 'some-library';
const someLibrary = require('some-library') as SomeLibraryType;
// Option 2: Use proper type declarations
interface SomeLibrary {
doSomething(): string;
configure(options: any): void;
}
const someLibrary: SomeLibrary = require('some-library');
// Option 3: Create typed wrapper
const createTypedRequire = <T>(modulePath: string): T => {
return require(modulePath);
};
const typedLibrary = createTypedRequire<SomeLibrary>('some-library');
๐ ๏ธ Best Practices
- ๐ฏ Use Consistent Module Systems: Pick CommonJS or ES modules per project
- ๐ Configure TypeScript Properly: Set correct module and resolution options
- ๐ก๏ธ Maintain Type Safety: Donโt lose types when using require()
- ๐จ Use esModuleInterop: Enable seamless ES/CommonJS compatibility
- โจ Cache Modules Wisely: Use require() caching effectively
- ๐ Handle Errors Gracefully: Wrap require() calls in try/catch
- ๐ฆ Export Consistently: Use the same export pattern throughout
๐งช Hands-On Exercise
๐ฏ Challenge: Build a Plugin-Based Logger System
Create a modular logging system that supports dynamic plugin loading:
๐ Requirements:
- ๐ Core logger with multiple log levels (info, warn, error, debug)
- ๐ Plugin system for different output formats (console, file, database)
- โ๏ธ Configuration system using CommonJS modules
- ๐ Dynamic plugin loading and unloading
- ๐ Log aggregation and filtering capabilities
- ๐จ TypeScript types for all plugins and configurations!
๐ Bonus Points:
- Add log rotation for file outputs
- Implement log streaming to external services
- Create performance monitoring plugins
๐ก Solution
๐ Click to see solution
// ๐ core/logger.ts - Main logger implementation
interface LogLevel {
name: string;
level: number;
color?: string;
}
interface LogEntry {
timestamp: Date;
level: LogLevel;
message: string;
metadata?: Record<string, any>;
source?: string;
}
interface LoggerPlugin {
name: string;
version: string;
init(config?: any): void;
log(entry: LogEntry): void;
destroy?(): void;
}
class PluginLogger {
private levels: Map<string, LogLevel> = new Map();
private plugins: Map<string, LoggerPlugin> = new Map();
private currentLevel: number = 0;
constructor() {
// ๐ Setup default log levels
this.addLevel('debug', 0, '\x1b[36m');
this.addLevel('info', 1, '\x1b[32m');
this.addLevel('warn', 2, '\x1b[33m');
this.addLevel('error', 3, '\x1b[31m');
}
// ๐ Add log level
addLevel(name: string, level: number, color?: string): void {
this.levels.set(name, { name, level, color });
console.log(`๐ Added log level: ${name} (${level})`);
}
// ๐ Load plugin
loadPlugin(name: string, pluginPath: string, config?: any): void {
try {
const PluginClass = require(pluginPath);
const plugin: LoggerPlugin = new PluginClass();
plugin.init(config);
this.plugins.set(name, plugin);
console.log(`๐ Plugin loaded: ${name} v${plugin.version}`);
} catch (error) {
console.error(`โ Failed to load plugin: ${name}`, error);
}
}
// ๐ Core logging method
private logInternal(levelName: string, message: string, metadata?: any): void {
const level = this.levels.get(levelName);
if (!level || level.level < this.currentLevel) {
return; // Level too low
}
const entry: LogEntry = {
timestamp: new Date(),
level,
message,
metadata,
source: this.getCallerInfo()
};
// ๐ก Send to all plugins
for (const plugin of this.plugins.values()) {
try {
plugin.log(entry);
} catch (error) {
console.error(`โ Plugin error: ${plugin.name}`, error);
}
}
}
// ๐ Get caller information
private getCallerInfo(): string {
const stack = new Error().stack;
if (stack) {
const lines = stack.split('\n');
return lines[4]?.trim() || 'unknown';
}
return 'unknown';
}
// ๐ Public logging methods
debug(message: string, metadata?: any): void {
this.logInternal('debug', message, metadata);
}
info(message: string, metadata?: any): void {
this.logInternal('info', message, metadata);
}
warn(message: string, metadata?: any): void {
this.logInternal('warn', message, metadata);
}
error(message: string, metadata?: any): void {
this.logInternal('error', message, metadata);
}
// โ๏ธ Set minimum log level
setLevel(levelName: string): void {
const level = this.levels.get(levelName);
if (level) {
this.currentLevel = level.level;
console.log(`โ๏ธ Log level set to: ${levelName}`);
}
}
}
// ๐ฆ Export logger
module.exports = PluginLogger;
// ๐ plugins/console-plugin.ts - Console output plugin
const ConsolePlugin = class implements LoggerPlugin {
name = 'console';
version = '1.0.0';
private showColors = true;
init(config: { colors?: boolean } = {}): void {
this.showColors = config.colors !== false;
console.log(`๐ฅ๏ธ Console plugin initialized (colors: ${this.showColors})`);
}
log(entry: LogEntry): void {
const timestamp = entry.timestamp.toISOString();
const level = entry.level.name.toUpperCase();
let output = `[${timestamp}] ${level}: ${entry.message}`;
if (entry.metadata) {
output += ` ${JSON.stringify(entry.metadata)}`;
}
if (this.showColors && entry.level.color) {
output = `${entry.level.color}${output}\x1b[0m`;
}
console.log(output);
}
};
module.exports = ConsolePlugin;
// ๐ plugins/file-plugin.ts - File output plugin
const fs = require('fs');
const path = require('path');
const FilePlugin = class implements LoggerPlugin {
name = 'file';
version = '1.0.0';
private logDir = './logs';
private maxSize = 10 * 1024 * 1024; // 10MB
init(config: { logDir?: string; maxSize?: number } = {}): void {
this.logDir = config.logDir || this.logDir;
this.maxSize = config.maxSize || this.maxSize;
// ๐ Ensure log directory exists
if (!fs.existsSync(this.logDir)) {
fs.mkdirSync(this.logDir, { recursive: true });
}
console.log(`๐ File plugin initialized (dir: ${this.logDir})`);
}
log(entry: LogEntry): void {
const date = entry.timestamp.toISOString().split('T')[0];
const filename = path.join(this.logDir, `app-${date}.log`);
const logLine = JSON.stringify({
timestamp: entry.timestamp.toISOString(),
level: entry.level.name,
message: entry.message,
metadata: entry.metadata,
source: entry.source
}) + '\n';
// ๐ Check file size and rotate if needed
this.rotateIfNeeded(filename);
fs.appendFileSync(filename, logLine);
}
private rotateIfNeeded(filename: string): void {
if (fs.existsSync(filename)) {
const stats = fs.statSync(filename);
if (stats.size > this.maxSize) {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const rotated = `${filename}.${timestamp}`;
fs.renameSync(filename, rotated);
console.log(`๐ Rotated log file: ${rotated}`);
}
}
}
};
module.exports = FilePlugin;
// ๐ config/logger-config.ts - Logger configuration
const loggerConfig = {
level: 'info',
plugins: [
{
name: 'console',
path: './plugins/console-plugin',
config: {
colors: true
}
},
{
name: 'file',
path: './plugins/file-plugin',
config: {
logDir: './logs',
maxSize: 5 * 1024 * 1024 // 5MB
}
}
]
};
module.exports = loggerConfig;
// ๐ index.ts - Usage example
const PluginLogger = require('./core/logger');
const config = require('./config/logger-config');
// ๐ Initialize logger
const logger = new PluginLogger();
// ๐ Load plugins from config
for (const pluginConfig of config.plugins) {
logger.loadPlugin(
pluginConfig.name,
pluginConfig.path,
pluginConfig.config
);
}
// โ๏ธ Set log level
logger.setLevel(config.level);
// ๐ Test logging
logger.info('Logger system initialized! ๐');
logger.debug('Debug information', { userId: 123, action: 'login' });
logger.warn('This is a warning โ ๏ธ', { component: 'auth' });
logger.error('An error occurred! โ', { error: 'Connection failed' });
// ๐ฏ Export configured logger
module.exports = logger;
๐ Key Takeaways
Youโve mastered CommonJS modules in TypeScript! Hereโs what you can now do:
- โ Use require() and module.exports with full TypeScript support ๐ช
- โ Configure TypeScript for seamless Node.js compatibility ๐ก๏ธ
- โ Bridge ES modules and CommonJS without losing type safety ๐ฏ
- โ Build universal packages that work in any environment ๐ง
- โ Handle legacy codebases with confidence and skill ๐
Remember: CommonJS is still the backbone of the Node.js ecosystem. Mastering it opens doors to countless libraries and legacy systems! ๐ค
๐ค Next Steps
Congratulations! ๐ Youโve mastered CommonJS modules in TypeScript!
Hereโs what to do next:
- ๐ป Practice with the plugin logger exercise above
- ๐๏ธ Convert an existing ES module project to CommonJS
- ๐ Move on to our next tutorial: Promises in TypeScript: Type-Safe Asynchronous Code
- ๐ Share your universal packages with the community!
Remember: The best developers understand both old and new technologies. Keep building bridges! ๐
Happy CommonJS coding! ๐๐ฅ๏ธโจ