Docs

README

14.5 Module Patterns

📖 Introduction

Module patterns are architectural approaches for organizing code using JavaScript modules. This section covers common patterns that leverage ES Modules to create maintainable, scalable applications.

┌─────────────────────────────────────────────────────────────────────────────┐
│                       MODULE PATTERNS OVERVIEW                              │
├─────────────────────────────────────────────────────────────────────────────┤
│                                                                             │
│   ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐        │
│   │   SINGLETON     │    │    FACTORY      │    │    FACADE       │        │
│   │                 │    │                 │    │                 │        │
│   │  One instance   │    │  Create objects │    │  Simplify API   │        │
│   │  shared across  │    │  with hidden    │    │  for complex    │        │
│   │  entire app     │    │  implementation │    │  subsystems     │        │
│   └─────────────────┘    └─────────────────┘    └─────────────────┘        │
│                                                                             │
│   ┌─────────────────┐    ┌─────────────────┐    ┌─────────────────┐        │
│   │    ADAPTER      │    │   DEPENDENCY    │    │   BARREL/       │        │
│   │                 │    │   INJECTION     │    │   RE-EXPORT     │        │
│   │  Convert one    │    │                 │    │                 │        │
│   │  interface to   │    │  Provide deps   │    │  Aggregate &    │        │
│   │  another        │    │  from outside   │    │  expose modules │        │
│   └─────────────────┘    └─────────────────┘    └─────────────────┘        │
│                                                                             │
└─────────────────────────────────────────────────────────────────────────────┘

🎯 Learning Objectives

By the end of this section, you will:

  • ✅ Implement the Singleton pattern with modules
  • ✅ Create factory modules
  • ✅ Build facade patterns for complex systems
  • ✅ Use adapter patterns for API compatibility
  • ✅ Implement dependency injection
  • ✅ Organize code with barrel files
  • ✅ Apply module best practices

1️⃣ Singleton Pattern

ES Modules Are Natural Singletons

// ═══════════════════════════════════════════════════════════════
// config.js - Module-level Singleton
// ═══════════════════════════════════════════════════════════════

// This object is created ONCE when module is first imported
// All subsequent imports get the SAME instance

const config = {
  apiUrl: 'https://api.example.com',
  timeout: 5000,
  retries: 3,
  debug: process.env.NODE_ENV === 'development',
};

// Freeze to prevent modifications (true singleton)
Object.freeze(config);

export default config;

// ═══════════════════════════════════════════════════════════════
// Usage in multiple files - same instance
// ═══════════════════════════════════════════════════════════════

// fileA.js
import config from './config.js';
console.log(config.apiUrl); // https://api.example.com

// fileB.js
import config from './config.js';
console.log(config === configFromFileA); // true (same object!)
┌─────────────────────────────────────────────────────────────────────┐
│                    SINGLETON WITH MODULES                           │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   WHY MODULES ARE SINGLETONS:                                       │
│                                                                     │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │                                                              │  │
│   │   1. First import of config.js                               │  │
│   │      ↓                                                       │  │
│   │   2. Module code executes ONCE                               │  │
│   │      ↓                                                       │  │
│   │   3. Exports are cached                                      │  │
│   │      ↓                                                       │  │
│   │   4. Second import of config.js                              │  │
│   │      ↓                                                       │  │
│   │   5. Returns cached exports (NO re-execution!)               │  │
│   │                                                              │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   COMMON SINGLETON USE CASES:                                       │
│   • Configuration                                                   │
│   • Database connections                                            │
│   • Logging service                                                 │
│   • State management (stores)                                       │
│   • Event bus                                                       │
│   • Cache                                                           │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Stateful Singleton

// ═══════════════════════════════════════════════════════════════
// store.js - Singleton state store
// ═══════════════════════════════════════════════════════════════

// Private state (not exported)
let state = {
  user: null,
  isAuthenticated: false,
  theme: 'light',
};

// Subscribers for state changes
const subscribers = new Set();

// Public API
export const store = {
  // Get current state (return copy to prevent direct mutation)
  getState() {
    return { ...state };
  },

  // Update state
  setState(updates) {
    state = { ...state, ...updates };
    this.notify();
  },

  // Subscribe to changes
  subscribe(callback) {
    subscribers.add(callback);
    // Return unsubscribe function
    return () => subscribers.delete(callback);
  },

  // Notify all subscribers
  notify() {
    subscribers.forEach((callback) => callback(this.getState()));
  },
};

// Freeze the API (not the state)
Object.freeze(store);

// ═══════════════════════════════════════════════════════════════
// Usage
// ═══════════════════════════════════════════════════════════════

// component.js
import { store } from './store.js';

// Subscribe to changes
const unsubscribe = store.subscribe((newState) => {
  console.log('State changed:', newState);
});

// Update state
store.setState({ user: { name: 'Alice' }, isAuthenticated: true });

// Later: unsubscribe
unsubscribe();

2️⃣ Factory Pattern

Creating Objects with Hidden Implementation

// ═══════════════════════════════════════════════════════════════
// userFactory.js - Factory module
// ═══════════════════════════════════════════════════════════════

// Private counter (not exported)
let nextId = 1;

// Private validation (not exported)
function validateEmail(email) {
  return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
}

// Private default values
const defaults = {
  role: 'user',
  active: true,
  createdAt: () => new Date(),
};

// Factory function (exported)
export function createUser({ name, email, role = defaults.role }) {
  // Validation
  if (!name || typeof name !== 'string') {
    throw new Error('Name is required');
  }
  if (!validateEmail(email)) {
    throw new Error('Invalid email format');
  }

  // Create user object with private id
  const id = nextId++;

  return {
    id,
    name,
    email,
    role,
    active: defaults.active,
    createdAt: defaults.createdAt(),

    // Methods
    deactivate() {
      this.active = false;
    },
    promote() {
      this.role = 'admin';
    },
    toJSON() {
      return { id, name, email, role, active: this.active };
    },
  };
}

// Factory for admins
export function createAdmin(userData) {
  const user = createUser({ ...userData, role: 'admin' });

  return {
    ...user,
    permissions: ['read', 'write', 'delete', 'admin'],

    grantPermission(permission) {
      if (!this.permissions.includes(permission)) {
        this.permissions.push(permission);
      }
    },
  };
}

// ═══════════════════════════════════════════════════════════════
// Usage
// ═══════════════════════════════════════════════════════════════

import { createUser, createAdmin } from './userFactory.js';

const user1 = createUser({ name: 'Alice', email: 'alice@example.com' });
const user2 = createUser({ name: 'Bob', email: 'bob@example.com' });
const admin = createAdmin({ name: 'Charlie', email: 'charlie@example.com' });

console.log(user1.id); // 1
console.log(user2.id); // 2
console.log(admin.id); // 3
┌─────────────────────────────────────────────────────────────────────┐
│                      FACTORY PATTERN                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌──────────────────────────────────────────────────────────────┐ │
│   │                      FACTORY MODULE                           │ │
│   │  ┌────────────────────────────────────────────────────────┐  │ │
│   │  │  PRIVATE (not exported)                                 │  │ │
│   │  │  • let counter = 0;                                     │  │ │
│   │  │  • function validate() {...}                            │  │ │
│   │  │  • const defaults = {...}                               │  │ │
│   │  └────────────────────────────────────────────────────────┘  │ │
│   │                           │                                   │ │
│   │                           ▼                                   │ │
│   │  ┌────────────────────────────────────────────────────────┐  │ │
│   │  │  PUBLIC (exported)                                      │  │ │
│   │  │  • export function createThing() {...}                  │  │ │
│   │  │  • export function createSpecialThing() {...}           │  │ │
│   │  └────────────────────────────────────────────────────────┘  │ │
│   └──────────────────────────────────────────────────────────────┘ │
│                                                                     │
│   BENEFITS:                                                         │
│   • Hide implementation details                                     │
│   • Consistent object creation                                      │
│   • Validation in one place                                         │
│   • Easy to extend/modify                                           │
│   • Can track instances (counter)                                   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

Async Factory

// ═══════════════════════════════════════════════════════════════
// connectionFactory.js - Async factory
// ═══════════════════════════════════════════════════════════════

// Private connection pool
const pool = new Map();

// Private config
const defaultConfig = {
  host: 'localhost',
  port: 5432,
  maxConnections: 10,
};

// Async factory function
export async function createConnection(options = {}) {
  const config = { ...defaultConfig, ...options };
  const key = `${config.host}:${config.port}`;

  // Return existing connection if available
  if (pool.has(key) && pool.get(key).isAlive()) {
    return pool.get(key);
  }

  // Create new connection
  console.log(`Connecting to ${key}...`);

  // Simulate async connection
  await new Promise((resolve) => setTimeout(resolve, 100));

  const connection = {
    config,
    connected: true,

    isAlive() {
      return this.connected;
    },

    async query(sql) {
      if (!this.connected) throw new Error('Not connected');
      console.log(`Executing: ${sql}`);
      return { rows: [], sql };
    },

    async close() {
      this.connected = false;
      pool.delete(key);
      console.log(`Connection to ${key} closed`);
    },
  };

  pool.set(key, connection);
  return connection;
}

// Cleanup helper
export async function closeAllConnections() {
  const connections = Array.from(pool.values());
  await Promise.all(connections.map((conn) => conn.close()));
}

3️⃣ Facade Pattern

Simplifying Complex Subsystems

// ═══════════════════════════════════════════════════════════════
// Complex subsystem modules
// ═══════════════════════════════════════════════════════════════

// auth/token.js
export function generateToken(user) {
  return `token_${user.id}_${Date.now()}`;
}

export function verifyToken(token) {
  return token && token.startsWith('token_');
}

// auth/session.js
const sessions = new Map();

export function createSession(userId, token) {
  const session = { userId, token, createdAt: new Date() };
  sessions.set(userId, session);
  return session;
}

export function getSession(userId) {
  return sessions.get(userId);
}

export function destroySession(userId) {
  sessions.delete(userId);
}

// auth/password.js
export async function hashPassword(password) {
  // Simulated hashing
  return `hashed_${password}`;
}

export async function verifyPassword(password, hash) {
  return hash === `hashed_${password}`;
}

// users/repository.js
const users = new Map();

export function findByEmail(email) {
  return Array.from(users.values()).find((u) => u.email === email);
}

export function findById(id) {
  return users.get(id);
}

export function save(user) {
  users.set(user.id, user);
  return user;
}

// ═══════════════════════════════════════════════════════════════
// auth.js - FACADE that simplifies all the above
// ═══════════════════════════════════════════════════════════════

import { generateToken, verifyToken } from './auth/token.js';
import { createSession, getSession, destroySession } from './auth/session.js';
import { hashPassword, verifyPassword } from './auth/password.js';
import { findByEmail, findById, save } from './users/repository.js';

// Simple, unified API
export const auth = {
  async register(email, password, name) {
    // Check if user exists
    if (findByEmail(email)) {
      throw new Error('Email already registered');
    }

    // Hash password and create user
    const hashedPassword = await hashPassword(password);
    const user = save({
      id: Date.now(),
      email,
      name,
      password: hashedPassword,
    });

    // Auto-login after registration
    return this.login(email, password);
  },

  async login(email, password) {
    // Find user
    const user = findByEmail(email);
    if (!user) {
      throw new Error('Invalid credentials');
    }

    // Verify password
    const valid = await verifyPassword(password, user.password);
    if (!valid) {
      throw new Error('Invalid credentials');
    }

    // Create session
    const token = generateToken(user);
    createSession(user.id, token);

    return { user: { id: user.id, email, name: user.name }, token };
  },

  logout(userId) {
    destroySession(userId);
  },

  isAuthenticated(userId) {
    const session = getSession(userId);
    return session && verifyToken(session.token);
  },

  getCurrentUser(userId) {
    if (!this.isAuthenticated(userId)) return null;
    const user = findById(userId);
    return user ? { id: user.id, email: user.email, name: user.name } : null;
  },
};

// ═══════════════════════════════════════════════════════════════
// Usage - Simple API hides complexity
// ═══════════════════════════════════════════════════════════════

import { auth } from './auth.js';

// Simple usage - don't need to know about tokens, sessions, hashing
const result = await auth.register('alice@example.com', 'password123', 'Alice');
console.log(result.token);

const loginResult = await auth.login('alice@example.com', 'password123');
console.log(auth.isAuthenticated(loginResult.user.id)); // true

auth.logout(loginResult.user.id);
console.log(auth.isAuthenticated(loginResult.user.id)); // false
┌─────────────────────────────────────────────────────────────────────┐
│                       FACADE PATTERN                                │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   WITHOUT FACADE:                   WITH FACADE:                    │
│                                                                     │
│   // Client must know all          // Client uses simple API        │
│   // subsystem details             import { auth } from './auth'    │
│                                                                     │
│   import { token }                  auth.login(email, pass);        │
│   import { session }                auth.logout(userId);            │
│   import { password }               auth.isAuthenticated(id);       │
│   import { user }                                                   │
│                                                                     │
│   const hash = password.hash()                                      │
│   const user = user.find()                                          │
│   const tok = token.generate()                                      │
│   session.create(...)                                               │
│                                                                     │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   ┌────────────────────────────────────────────────────────────┐   │
│   │                         CLIENT                              │   │
│   │                           │                                 │   │
│   │                           ▼                                 │   │
│   │              ┌──────────────────────┐                       │   │
│   │              │       FACADE         │ ◀── Simple API       │   │
│   │              └──────────────────────┘                       │   │
│   │                    │    │    │                              │   │
│   │          ┌─────────┘    │    └─────────┐                    │   │
│   │          ▼              ▼              ▼                    │   │
│   │   ┌──────────┐   ┌──────────┐   ┌──────────┐               │   │
│   │   │ Subsys A │   │ Subsys B │   │ Subsys C │               │   │
│   │   └──────────┘   └──────────┘   └──────────┘               │   │
│   │                                                             │   │
│   └────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

4️⃣ Adapter Pattern

Converting Interfaces

// ═══════════════════════════════════════════════════════════════
// Different analytics libraries with different APIs
// ═══════════════════════════════════════════════════════════════

// googleAnalytics.js - Google Analytics API
export const ga = {
  send(hitType, eventCategory, eventAction, eventLabel) {
    console.log(
      `GA: ${hitType} - ${eventCategory}/${eventAction}/${eventLabel}`
    );
  },
  pageview(path) {
    console.log(`GA Pageview: ${path}`);
  },
};

// mixpanel.js - Mixpanel API (different structure!)
export const mixpanel = {
  track(eventName, properties) {
    console.log(`Mixpanel: ${eventName}`, properties);
  },
  page(pageName, properties) {
    console.log(`Mixpanel Page: ${pageName}`, properties);
  },
};

// ═══════════════════════════════════════════════════════════════
// analyticsAdapter.js - Unified interface
// ═══════════════════════════════════════════════════════════════

import { ga } from './googleAnalytics.js';
import { mixpanel } from './mixpanel.js';

// Adapter for Google Analytics
function createGAAdapter() {
  return {
    trackEvent(name, properties = {}) {
      ga.send(
        'event',
        properties.category || 'general',
        name,
        properties.label
      );
    },
    trackPageView(path, title) {
      ga.pageview(path);
    },
  };
}

// Adapter for Mixpanel
function createMixpanelAdapter() {
  return {
    trackEvent(name, properties = {}) {
      mixpanel.track(name, properties);
    },
    trackPageView(path, title) {
      mixpanel.page(title || path, { path });
    },
  };
}

// Factory to get the right adapter
export function createAnalyticsAdapter(provider = 'ga') {
  switch (provider) {
    case 'ga':
    case 'google':
      return createGAAdapter();
    case 'mixpanel':
      return createMixpanelAdapter();
    default:
      throw new Error(`Unknown provider: ${provider}`);
  }
}

// Default export with unified interface
const defaultAdapter = createGAAdapter();

export const analytics = {
  trackEvent: (name, props) => defaultAdapter.trackEvent(name, props),
  trackPageView: (path, title) => defaultAdapter.trackPageView(path, title),
};

// ═══════════════════════════════════════════════════════════════
// Usage - Same code works with any analytics provider
// ═══════════════════════════════════════════════════════════════

import { analytics } from './analyticsAdapter.js';

// Same interface regardless of underlying provider
analytics.trackEvent('button_click', { category: 'ui', label: 'submit' });
analytics.trackPageView('/dashboard', 'Dashboard');

5️⃣ Dependency Injection

Providing Dependencies from Outside

// ═══════════════════════════════════════════════════════════════
// Without DI - Hard to test, tightly coupled
// ═══════════════════════════════════════════════════════════════

// userService.js (BAD - imports dependencies directly)
import { db } from './database.js';
import { logger } from './logger.js';
import { mailer } from './mailer.js';

export async function createUser(userData) {
  logger.info('Creating user...');
  const user = await db.users.create(userData);
  await mailer.send(user.email, 'Welcome!');
  return user;
}

// ═══════════════════════════════════════════════════════════════
// With DI - Dependencies passed in
// ═══════════════════════════════════════════════════════════════

// userService.js (GOOD - receives dependencies)
export function createUserService({ db, logger, mailer }) {
  return {
    async createUser(userData) {
      logger.info('Creating user...');
      const user = await db.users.create(userData);
      await mailer.send(user.email, 'Welcome!');
      return user;
    },

    async findUser(id) {
      return db.users.findById(id);
    },

    async deleteUser(id) {
      logger.info(`Deleting user ${id}...`);
      return db.users.delete(id);
    },
  };
}

// ═══════════════════════════════════════════════════════════════
// Composition root - Wire everything together
// ═══════════════════════════════════════════════════════════════

// container.js
import { createDatabase } from './database.js';
import { createLogger } from './logger.js';
import { createMailer } from './mailer.js';
import { createUserService } from './userService.js';
import { createOrderService } from './orderService.js';

// Create instances
const logger = createLogger({ level: 'info' });
const db = createDatabase({ connectionString: process.env.DB_URL });
const mailer = createMailer({ apiKey: process.env.MAIL_API_KEY });

// Inject dependencies
export const userService = createUserService({ db, logger, mailer });
export const orderService = createOrderService({ db, logger, userService });

// ═══════════════════════════════════════════════════════════════
// Testing with mock dependencies
// ═══════════════════════════════════════════════════════════════

// userService.test.js
import { createUserService } from './userService.js';

describe('UserService', () => {
  it('should create a user', async () => {
    // Create mock dependencies
    const mockDb = {
      users: {
        create: jest.fn().mockResolvedValue({ id: 1, email: 'test@test.com' }),
      },
    };
    const mockLogger = { info: jest.fn() };
    const mockMailer = { send: jest.fn().mockResolvedValue(true) };

    // Inject mocks
    const userService = createUserService({
      db: mockDb,
      logger: mockLogger,
      mailer: mockMailer,
    });

    // Test
    const user = await userService.createUser({ email: 'test@test.com' });

    expect(mockDb.users.create).toHaveBeenCalled();
    expect(mockMailer.send).toHaveBeenCalledWith('test@test.com', 'Welcome!');
    expect(user.id).toBe(1);
  });
});
┌─────────────────────────────────────────────────────────────────────┐
│                    DEPENDENCY INJECTION                             │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   WITHOUT DI:                       WITH DI:                        │
│                                                                     │
│   Module imports its               Dependencies passed in           │
│   own dependencies                  from outside                    │
│                                                                     │
│   ┌──────────────┐                 ┌──────────────┐                │
│   │   Service    │                 │   Service    │                │
│   │    │         │                 │      ▲       │                │
│   │    ├── DB ◀──┤ Hard-coded      │      │       │ Injected       │
│   │    ├── Log ◀─┤                 │ { db, log }  │                │
│   │    └── Mail◀─┤                 │              │                │
│   └──────────────┘                 └──────────────┘                │
│                                                                     │
│   Problems:                         Benefits:                       │
│   • Hard to test                    • Easy to test (mock deps)     │
│   • Tightly coupled                 • Loosely coupled              │
│   • Hard to change deps             • Swap implementations         │
│                                                                     │
│   COMPOSITION ROOT:                                                 │
│   ┌────────────────────────────────────────────────────────────┐   │
│   │  // container.js - Wire everything once                    │   │
│   │  const db = createDB(config);                               │   │
│   │  const logger = createLogger();                             │   │
│   │  const userService = createUserService({ db, logger });     │   │
│   │  export { userService };                                    │   │
│   └────────────────────────────────────────────────────────────┘   │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

6️⃣ Barrel Pattern (Re-exports)

Organizing Public API

// ═══════════════════════════════════════════════════════════════
// Project structure with barrels
// ═══════════════════════════════════════════════════════════════

// src/
// ├── components/
// │   ├── Button.js
// │   ├── Input.js
// │   ├── Modal.js
// │   └── index.js       ← Barrel
// ├── hooks/
// │   ├── useAuth.js
// │   ├── useFetch.js
// │   └── index.js       ← Barrel
// ├── utils/
// │   ├── math.js
// │   ├── string.js
// │   └── index.js       ← Barrel
// └── index.js           ← Main barrel

// ═══════════════════════════════════════════════════════════════
// components/index.js - Component barrel
// ═══════════════════════════════════════════════════════════════

export { default as Button } from './Button.js';
export { default as Input } from './Input.js';
export { default as Modal } from './Modal.js';

// Re-export types (if using TypeScript)
export type { ButtonProps } from './Button.js';
export type { InputProps } from './Input.js';

// ═══════════════════════════════════════════════════════════════
// hooks/index.js - Hooks barrel
// ═══════════════════════════════════════════════════════════════

export { useAuth } from './useAuth.js';
export { useFetch } from './useFetch.js';
export { useLocalStorage } from './useLocalStorage.js';

// ═══════════════════════════════════════════════════════════════
// utils/index.js - Utils barrel with selective exports
// ═══════════════════════════════════════════════════════════════

// Only export public utilities
export { add, subtract, multiply } from './math.js';
export { capitalize, truncate } from './string.js';

// Don't export internal helpers
// import { internalHelper } from './internal.js';  // Keep private

// ═══════════════════════════════════════════════════════════════
// src/index.js - Main entry point
// ═══════════════════════════════════════════════════════════════

// Re-export everything from sub-barrels
export * from './components/index.js';
export * from './hooks/index.js';
export * from './utils/index.js';

// ═══════════════════════════════════════════════════════════════
// Usage - Clean imports
// ═══════════════════════════════════════════════════════════════

// Instead of:
import { Button } from './components/Button.js';
import { Input } from './components/Input.js';
import { useAuth } from './hooks/useAuth.js';

// Use:
import { Button, Input, useAuth } from './src';
// or
import { Button, Input } from './src/components';
import { useAuth, useFetch } from './src/hooks';
┌─────────────────────────────────────────────────────────────────────┐
│                      BARREL PATTERN                                 │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   STRUCTURE:                                                        │
│                                                                     │
│   src/                                                              │
│   ├── components/                                                   │
│   │   ├── Button.js    ──┐                                         │
│   │   ├── Input.js      ├──▶ index.js (barrel)                     │
│   │   └── Modal.js     ──┘       │                                 │
│   │                              ▼                                  │
│   └── index.js   ◀───────────────┘  (main barrel)                  │
│                                                                     │
│   BENEFITS:                                                         │
│   • Clean import paths                                              │
│   • Control public API                                              │
│   • Hide implementation details                                     │
│   • Easy refactoring                                                │
│                                                                     │
│   ⚠️  CAUTIONS:                                                     │
│   • Can hurt tree-shaking if importing from main barrel             │
│   • Deep barrels can slow build times                               │
│   • Circular dependency risk                                        │
│                                                                     │
│   BEST PRACTICES:                                                   │
│   • Use specific barrels: import from './components'               │
│   • Avoid re-exporting everything from main barrel                 │
│   • Keep barrels shallow (one level)                               │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

7️⃣ Module Best Practices

┌─────────────────────────────────────────────────────────────────────┐
│                    MODULE BEST PRACTICES                            │
├─────────────────────────────────────────────────────────────────────┤
│                                                                     │
│   1. SINGLE RESPONSIBILITY                                          │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  ❌ BAD: god-module.js (does everything)                    │  │
│   │  ✅ GOOD: auth.js, users.js, orders.js (focused modules)    │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   2. EXPLICIT EXPORTS                                               │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  ❌ BAD: export default { lots, of, things }                │  │
│   │  ✅ GOOD: export { specific, named, exports }               │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   3. AVOID SIDE EFFECTS                                             │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  ❌ BAD: console.log('loaded'); at top level               │  │
│   │  ❌ BAD: fetch('/api/init'); at top level                  │  │
│   │  ✅ GOOD: export function init() { fetch(...) }            │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   4. CONSISTENT STRUCTURE                                           │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  // Every module follows same pattern:                      │  │
│   │  // 1. Imports                                              │  │
│   │  // 2. Constants                                            │  │
│   │  // 3. Private helpers                                      │  │
│   │  // 4. Public exports                                       │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   5. AVOID CIRCULAR DEPENDENCIES                                    │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  ❌ A imports B, B imports A                                 │  │
│   │  ✅ Extract shared code to C, both import C                 │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   6. USE DEPENDENCY INJECTION                                       │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  ❌ import { db } from './db'  // Hard dependency          │  │
│   │  ✅ export function create({ db }) // Injected             │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
│   7. DOCUMENT PUBLIC API                                            │
│   ┌─────────────────────────────────────────────────────────────┐  │
│   │  /**                                                        │  │
│   │   * Creates a new user                                      │  │
│   │   * @param {Object} data - User data                        │  │
│   │   * @returns {Promise<User>}                                │  │
│   │   */                                                        │  │
│   │  export async function createUser(data) { ... }             │  │
│   └─────────────────────────────────────────────────────────────┘  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘

📚 Summary

PatternUse CaseKey Benefit
SingletonShared state, configSingle instance across app
FactoryObject creationEncapsulate creation logic
FacadeComplex subsystemsSimplified API
AdapterDifferent interfacesInterface compatibility
DITesting, flexibilityLoose coupling
BarrelModule organizationClean imports

📚 Module 14 Complete!

You've learned:

  • ✅ Module system evolution (IIFE → CommonJS → AMD → ESM)
  • ✅ All ES Module syntax patterns
  • ✅ CommonJS and AMD for legacy code
  • ✅ Bundlers (Webpack, Vite, Rollup, esbuild)
  • ✅ Module architectural patterns

Next: Continue to Module 15: Asynchronous JavaScript to learn about Promises, async/await, and handling asynchronous operations.

README - JavaScript Tutorial | DeepML