12 Popular Design Patterns

Concise overview with short descriptions and tiny JavaScript examples.

1) Singleton (Creational)

Ensure a class has only one instance and provide a global access point.

Use: shared config/cache/loggerPros: one source of truth
Example
// === Node.js TypeScript ===
import { Pool, PoolClient } from 'pg';

class DatabaseService {
  private static instance: DatabaseService | null = null;
  private pool: Pool | null = null;
  private isConnected = false;

  private constructor() {
    // Private constructor to prevent direct instantiation
  }

  static getInstance(): DatabaseService {
    if (!DatabaseService.instance) {
      DatabaseService.instance = new DatabaseService();
    }
    return DatabaseService.instance;
  }

  async connect(config: { host: string; port: number; database: string; user: string; password: string }): Promise {
    if (this.isConnected) return;
    
    try {
      this.pool = new Pool(config);
      // Test connection
      const client = await this.pool.connect();
      client.release();
      this.isConnected = true;
    } catch (error) {
      throw new Error(`Database connection failed: ${error.message}`);
    }
  }

  async query(sql: string, params?: any[]): Promise {
    if (!this.pool) throw new Error('Database not connected. Call connect() first.');
    
    try {
      const result = await this.pool.query(sql, params);
      return result.rows;
    } catch (error) {
      console.error('Query error:', error);
      throw new Error(`Query execution failed: ${error.message}`);
    }
  }

  async transaction(callback: (client: PoolClient) => Promise): Promise {
    if (!this.pool) throw new Error('Database not connected');
    
    const client = await this.pool.connect();
    try {
      await client.query('BEGIN');
      const result = await callback(client);
      await client.query('COMMIT');
      return result;
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }

  async disconnect(): Promise {
    if (this.pool) {
      await this.pool.end();
      this.isConnected = false;
      this.pool = null;
    }
  }
}

// Usage - Only one instance throughout the application
const db1 = DatabaseService.getInstance();
const db2 = DatabaseService.getInstance();
console.log(db1 === db2); // true - same instance

await db1.connect({
  host: process.env.DB_HOST || 'localhost',
  port: parseInt(process.env.DB_PORT || '5432'),
  database: process.env.DB_NAME || 'mydb',
  user: process.env.DB_USER || 'postgres',
  password: process.env.DB_PASSWORD || ''
});

// Use in services
const users = await db1.query('SELECT * FROM users WHERE id = $1', [1]);

// === NestJS ===
import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { Pool } from 'pg';

@Injectable()
export class DatabaseService implements OnModuleInit, OnModuleDestroy {
  private pool: Pool;

  async onModuleInit() {
    this.pool = new Pool({
      host: process.env.DB_HOST,
      port: parseInt(process.env.DB_PORT),
      database: process.env.DB_NAME,
      user: process.env.DB_USER,
      password: process.env.DB_PASSWORD,
    });
  }

  async query(sql: string, params?: any[]): Promise {
    const result = await this.pool.query(sql, params);
    return result.rows;
  }

  async onModuleDestroy() {
    await this.pool.end();
  }
}

// NestJS providers are singleton-scoped by default
// All modules injecting DatabaseService get the same instance

2) Factory Method (Creational)

Define an interface for creating objects; subclasses decide which class to instantiate.

Use: choose variant at runtimePros: decouples construction
Example
// === Node.js TypeScript ===
import * as fs from 'fs/promises';
import * as path from 'path';

enum LogLevel {
  DEBUG = 'DEBUG',
  INFO = 'INFO',
  WARN = 'WARN',
  ERROR = 'ERROR'
}

interface ILogger {
  log(level: LogLevel, message: string, meta?: Record): void;
  info(message: string, meta?: Record): void;
  error(message: string, error?: Error, meta?: Record): void;
}

class ConsoleLogger implements ILogger {
  log(level: LogLevel, message: string, meta?: Record): void {
    const timestamp = new Date().toISOString();
    const logEntry = {
      timestamp,
      level,
      message,
      ...meta
    };
    
    if (level === LogLevel.ERROR) {
      console.error(JSON.stringify(logEntry));
    } else {
      console.log(JSON.stringify(logEntry));
    }
  }

  info(message: string, meta?: Record): void {
    this.log(LogLevel.INFO, message, meta);
  }

  error(message: string, error?: Error, meta?: Record): void {
    this.log(LogLevel.ERROR, message, {
      ...meta,
      error: error ? { message: error.message, stack: error.stack } : undefined
    });
  }
}

class FileLogger implements ILogger {
  private logDir: string;
  private logFile: string;

  constructor(logDir: string = './logs') {
    this.logDir = logDir;
    this.logFile = path.join(logDir, `app-${new Date().toISOString().split('T')[0]}.log`);
  }

  private async ensureLogDir(): Promise {
    try {
      await fs.mkdir(this.logDir, { recursive: true });
    } catch (error) {
      console.error('Failed to create log directory:', error);
    }
  }

  async log(level: LogLevel, message: string, meta?: Record): Promise {
    await this.ensureLogDir();
    
    const timestamp = new Date().toISOString();
    const logEntry = {
      timestamp,
      level,
      message,
      ...meta
    };

    try {
      await fs.appendFile(this.logFile, JSON.stringify(logEntry) + '\n');
    } catch (error) {
      console.error('Failed to write log:', error);
    }
  }

  info(message: string, meta?: Record): Promise {
    return this.log(LogLevel.INFO, message, meta);
  }

  async error(message: string, error?: Error, meta?: Record): Promise {
    await this.log(LogLevel.ERROR, message, {
      ...meta,
      error: error ? { message: error.message, stack: error.stack } : undefined
    });
  }
}

class CloudLogger implements ILogger {
  constructor(private apiKey: string, private endpoint: string) {}

  async log(level: LogLevel, message: string, meta?: Record): Promise {
    try {
      await fetch(this.endpoint, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
          'Authorization': `Bearer ${this.apiKey}`
        },
        body: JSON.stringify({
          level,
          message,
          timestamp: new Date().toISOString(),
          ...meta
        })
      });
    } catch (error) {
      console.error('Failed to send log to cloud:', error);
    }
  }

  info(message: string, meta?: Record): Promise {
    return this.log(LogLevel.INFO, message, meta);
  }

  error(message: string, error?: Error, meta?: Record): Promise {
    return this.log(LogLevel.ERROR, message, {
      ...meta,
      error: error ? { message: error.message, stack: error.stack } : undefined
    });
  }
}

// Factory function - decides which logger to create based on environment
function createLogger(env: 'dev'|'prod'|'cloud', config?: { logDir?: string; apiKey?: string; endpoint?: string }): ILogger {
  switch (env) {
    case 'prod':
      return new FileLogger(config?.logDir);
    case 'cloud':
      if (!config?.apiKey || !config?.endpoint) {
        throw new Error('Cloud logger requires apiKey and endpoint');
      }
      return new CloudLogger(config.apiKey, config.endpoint);
    case 'dev':
    default:
      return new ConsoleLogger();
  }
}

// Usage
const logger = createLogger(
  process.env.NODE_ENV === 'production' 
    ? process.env.LOG_TYPE === 'cloud' ? 'cloud' : 'prod'
    : 'dev',
  {
    logDir: process.env.LOG_DIR || './logs',
    apiKey: process.env.CLOUD_LOG_API_KEY,
    endpoint: process.env.CLOUD_LOG_ENDPOINT
  }
);

logger.info('Server started', { port: 3000, env: process.env.NODE_ENV });
logger.error('Database connection failed', new Error('Connection timeout'), { host: 'localhost' });

// === NestJS ===
import { Injectable, Module } from '@nestjs/common';

@Injectable()
export class LoggerFactory {
  create(env: 'dev'|'prod'|'cloud', config?: any): ILogger {
    return createLogger(env, config);
  }
}

const LOGGER_PROVIDER = {
  provide: 'LOGGER',
  useFactory: (): ILogger => {
    const env = process.env.NODE_ENV === 'production' 
      ? (process.env.LOG_TYPE === 'cloud' ? 'cloud' : 'prod')
      : 'dev';
    return createLogger(env, {
      logDir: process.env.LOG_DIR,
      apiKey: process.env.CLOUD_LOG_API_KEY,
      endpoint: process.env.CLOUD_LOG_ENDPOINT
    });
  },
};

@Module({ providers: [LOGGER_PROVIDER, LoggerFactory], exports: ['LOGGER'] })
export class LoggingModule {}

3) Abstract Factory (Creational)

Produce families of related objects without specifying concrete classes.

Use: theme/brand familiesPros: consistency across variants
Example
// === Node.js TypeScript ===
type User = { id: string; email: string; name: string; createdAt: Date };
type Order = { id: string; userId: string; total: number; status: string };

interface UserRepository {
  findById(id: string): Promise;
  findByEmail(email: string): Promise;
  create(user: Omit): Promise;
  update(id: string, data: Partial): Promise;
}

interface OrderRepository {
  findByUserId(userId: string): Promise;
  findById(id: string): Promise;
  create(order: Omit): Promise;
}

interface IRepositoryFactory {
  createUserRepository(): UserRepository;
  createOrderRepository(): OrderRepository;
  createTransaction(callback: () => Promise): Promise;
}

// PostgreSQL implementation
class SqlUserRepository implements UserRepository {
  constructor(private db: any) {}

  async findById(id: string): Promise {
    const result = await this.db.query(
      'SELECT * FROM users WHERE id = $1',
      [id]
    );
    return result.rows[0] || null;
  }

  async findByEmail(email: string): Promise {
    const result = await this.db.query(
      'SELECT * FROM users WHERE email = $1',
      [email]
    );
    return result.rows[0] || null;
  }

  async create(user: Omit): Promise {
    const result = await this.db.query(
      `INSERT INTO users (email, name, created_at) 
       VALUES ($1, $2, NOW()) RETURNING *`,
      [user.email, user.name]
    );
    return result.rows[0];
  }

  async update(id: string, data: Partial): Promise {
    const fields = Object.keys(data).map((key, i) => `${key} = $${i + 2}`).join(', ');
    const result = await this.db.query(
      `UPDATE users SET ${fields} WHERE id = $1 RETURNING *`,
      [id, ...Object.values(data)]
    );
    if (!result.rows[0]) throw new Error('User not found');
    return result.rows[0];
  }
}

class SqlOrderRepository implements OrderRepository {
  constructor(private db: any) {}

  async findByUserId(userId: string): Promise {
    const result = await this.db.query(
      'SELECT * FROM orders WHERE user_id = $1 ORDER BY created_at DESC',
      [userId]
    );
    return result.rows;
  }

  async findById(id: string): Promise {
    const result = await this.db.query(
      'SELECT * FROM orders WHERE id = $1',
      [id]
    );
    return result.rows[0] || null;
  }

  async create(order: Omit): Promise {
    const result = await this.db.query(
      `INSERT INTO orders (user_id, total, status, created_at) 
       VALUES ($1, $2, $3, NOW()) RETURNING *`,
      [order.userId, order.total, order.status]
    );
    return result.rows[0];
  }
}

class SqlRepositoryFactory implements IRepositoryFactory {
  constructor(private dbPool: any) {}

  createUserRepository(): UserRepository {
    return new SqlUserRepository(this.dbPool);
  }

  createOrderRepository(): OrderRepository {
    return new SqlOrderRepository(this.dbPool);
  }

  async createTransaction(callback: () => Promise): Promise {
    const client = await this.dbPool.connect();
    try {
      await client.query('BEGIN');
      // Create repositories with transaction client
      const userRepo = new SqlUserRepository(client);
      const orderRepo = new SqlOrderRepository(client);
      const result = await callback();
      await client.query('COMMIT');
      return result;
    } catch (error) {
      await client.query('ROLLBACK');
      throw error;
    } finally {
      client.release();
    }
  }
}

// MongoDB implementation
class MongoUserRepository implements UserRepository {
  constructor(private collection: any) {}

  async findById(id: string): Promise {
    return await this.collection.findOne({ _id: id });
  }

  async findByEmail(email: string): Promise {
    return await this.collection.findOne({ email });
  }

  async create(user: Omit): Promise {
    const doc = { ...user, _id: this.generateId(), createdAt: new Date() };
    await this.collection.insertOne(doc);
    return doc;
  }

  async update(id: string, data: Partial): Promise {
    const result = await this.collection.findOneAndUpdate(
      { _id: id },
      { $set: data },
      { returnDocument: 'after' }
    );
    if (!result.value) throw new Error('User not found');
    return result.value;
  }

  private generateId(): string {
    return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }
}

class MongoOrderRepository implements OrderRepository {
  constructor(private collection: any) {}

  async findByUserId(userId: string): Promise {
    return await this.collection.find({ userId }).sort({ createdAt: -1 }).toArray();
  }

  async findById(id: string): Promise {
    return await this.collection.findOne({ _id: id });
  }

  async create(order: Omit): Promise {
    const doc = { ...order, _id: this.generateId() };
    await this.collection.insertOne(doc);
    return doc;
  }

  private generateId(): string {
    return `${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
  }
}

class MongoRepositoryFactory implements IRepositoryFactory {
  constructor(private db: any) {}

  createUserRepository(): UserRepository {
    return new MongoUserRepository(this.db.collection('users'));
  }

  createOrderRepository(): OrderRepository {
    return new MongoOrderRepository(this.db.collection('orders'));
  }

  async createTransaction(callback: () => Promise): Promise {
    const session = this.db.startSession();
    try {
      session.startTransaction();
      // Repositories would need session context
      const result = await callback();
      await session.commitTransaction();
      return result;
    } catch (error) {
      await session.abortTransaction();
      throw error;
    } finally {
      session.endSession();
    }
  }
}

// Factory function to create the appropriate factory
function createRepositoryFactory(db: 'sql'|'mongo', dbConnection: any): IRepositoryFactory {
  return db === 'mongo' 
    ? new MongoRepositoryFactory(dbConnection)
    : new SqlRepositoryFactory(dbConnection);
}

// Usage
const dbType = process.env.DB_TYPE === 'mongo' ? 'mongo' : 'sql';
const dbConnection = dbType === 'mongo' ? mongoClient : pgPool;
const repoFactory = createRepositoryFactory(dbType, dbConnection);

const userRepo = repoFactory.createUserRepository();
const orderRepo = repoFactory.createOrderRepository();

// Use repositories
const user = await userRepo.findById('u1');
const orders = await orderRepo.findByUserId('u1');

// Transaction example
await repoFactory.createTransaction(async () => {
  const newUser = await userRepo.create({ email: 'test@example.com', name: 'Test' });
  await orderRepo.create({ userId: newUser.id, total: 100, status: 'pending' });
});

// === NestJS ===
import { Module, DynamicModule } from '@nestjs/common';

@Module({})
export class RepositoryModule {
  static register(db: 'sql'|'mongo', dbConnection: any): DynamicModule {
    const factory = createRepositoryFactory(db, dbConnection);
    
    const providers = [
      {
        provide: 'REPOSITORY_FACTORY',
        useValue: factory
      },
      {
        provide: 'USER_REPOSITORY',
        useFactory: (factory: IRepositoryFactory) => factory.createUserRepository(),
        inject: ['REPOSITORY_FACTORY']
      },
      {
        provide: 'ORDER_REPOSITORY',
        useFactory: (factory: IRepositoryFactory) => factory.createOrderRepository(),
        inject: ['REPOSITORY_FACTORY']
      }
    ];

    return {
      module: RepositoryModule,
      providers,
      exports: providers
    };
  }
}

4) Builder (Creational)

Step-by-step construction for complex objects with a clear fluent API.

Use: complex object setupPros: readable, flexible
Example
// === Node.js TypeScript ===
interface HttpHeaders {
  [key: string]: string | string[];
}

interface EmailMessage {
  to: string[];
  cc?: string[];
  bcc?: string[];
  subject: string;
  body: string;
  attachments?: Array<{ filename: string; content: Buffer; contentType?: string }>;
  priority?: 'low' | 'normal' | 'high';
  replyTo?: string;
}

class EmailMessageBuilder {
  private to: string[] = [];
  private cc: string[] = [];
  private bcc: string[] = [];
  private subject = '';
  private body = '';
  private attachments: Array<{ filename: string; content: Buffer; contentType?: string }> = [];
  private priority: 'low' | 'normal' | 'high' = 'normal';
  private replyTo?: string;

  addRecipient(email: string): this {
    if (!this.isValidEmail(email)) {
      throw new Error(`Invalid email address: ${email}`);
    }
    this.to.push(email);
    return this;
  }

  addRecipients(emails: string[]): this {
    emails.forEach(email => this.addRecipient(email));
    return this;
  }

  addCc(email: string): this {
    if (!this.isValidEmail(email)) {
      throw new Error(`Invalid email address: ${email}`);
    }
    this.cc.push(email);
    return this;
  }

  addBcc(email: string): this {
    if (!this.isValidEmail(email)) {
      throw new Error(`Invalid email address: ${email}`);
    }
    this.bcc.push(email);
    return this;
  }

  setSubject(subject: string): this {
    if (!subject || subject.trim().length === 0) {
      throw new Error('Subject cannot be empty');
    }
    this.subject = subject.trim();
    return this;
  }

  setBody(body: string): this {
    if (!body || body.trim().length === 0) {
      throw new Error('Body cannot be empty');
    }
    this.body = body;
    return this;
  }

  setHtmlBody(html: string): this {
    if (!html || html.trim().length === 0) {
      throw new Error('HTML body cannot be empty');
    }
    this.body = html;
    return this;
  }

  addAttachment(filename: string, content: Buffer, contentType?: string): this {
    if (!filename) {
      throw new Error('Filename is required for attachment');
    }
    this.attachments.push({ filename, content, contentType });
    return this;
  }

  setPriority(priority: 'low' | 'normal' | 'high'): this {
    this.priority = priority;
    return this;
  }

  setReplyTo(email: string): this {
    if (!this.isValidEmail(email)) {
      throw new Error(`Invalid reply-to email address: ${email}`);
    }
    this.replyTo = email;
    return this;
  }

  build(): EmailMessage {
    // Validation before building
    if (this.to.length === 0) {
      throw new Error('At least one recipient is required');
    }
    if (!this.subject) {
      throw new Error('Subject is required');
    }
    if (!this.body) {
      throw new Error('Body is required');
    }

    return {
      to: [...this.to],
      cc: this.cc.length > 0 ? [...this.cc] : undefined,
      bcc: this.bcc.length > 0 ? [...this.bcc] : undefined,
      subject: this.subject,
      body: this.body,
      attachments: this.attachments.length > 0 ? [...this.attachments] : undefined,
      priority: this.priority,
      replyTo: this.replyTo
    };
  }

  private isValidEmail(email: string): boolean {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    return emailRegex.test(email);
  }
}

// Usage example
const email = new EmailMessageBuilder()
  .addRecipient('customer@example.com')
  .addCc('manager@example.com')
  .setSubject('Order Confirmation #12345')
  .setHtmlBody(`
    

Thank you for your order!

Your order #12345 has been confirmed.

Total: $99.99

`) .addAttachment('invoice.pdf', Buffer.from('pdf content...'), 'application/pdf') .setPriority('high') .setReplyTo('support@example.com') .build(); // === NestJS === import { Injectable } from '@nestjs/common'; @Injectable() export class EmailMessageBuilder { // Same implementation as above } @Injectable() export class EmailService { constructor(private emailBuilder: EmailMessageBuilder) {} async sendWelcomeEmail(userEmail: string, userName: string): Promise { const email = this.emailBuilder .addRecipient(userEmail) .setSubject(`Welcome to our platform, ${userName}!`) .setHtmlBody(`

Welcome ${userName}!

Thank you for joining us.

`) .setPriority('normal') .build(); // Send email using email service await this.sendEmail(email); } private async sendEmail(message: EmailMessage): Promise { // Implementation to send email via SMTP/API } } // === Another Builder Example: Query Builder === class SqlQueryBuilder { private table = ''; private selects: string[] = ['*']; private wheres: Array<{ column: string; operator: string; value: any }> = []; private joins: Array<{ type: string; table: string; on: string }> = []; private orderBy?: { column: string; direction: 'ASC' | 'DESC' }; private limit?: number; private offset?: number; from(table: string): this { if (!table || table.trim().length === 0) { throw new Error('Table name cannot be empty'); } this.table = table; return this; } select(columns: string[]): this { this.selects = columns; return this; } where(column: string, operator: string, value: any): this { this.wheres.push({ column, operator, value }); return this; } join(type: 'INNER' | 'LEFT' | 'RIGHT', table: string, on: string): this { this.joins.push({ type, table, on }); return this; } orderBy(column: string, direction: 'ASC' | 'DESC' = 'ASC'): this { this.orderBy = { column, direction }; return this; } limit(limit: number): this { if (limit <= 0) { throw new Error('Limit must be greater than 0'); } this.limit = limit; return this; } offset(offset: number): this { if (offset < 0) { throw new Error('Offset must be greater than or equal to 0'); } this.offset = offset; return this; } build(): { sql: string; params: any[] } { if (!this.table) { throw new Error('Table name is required'); } const params: any[] = []; let sql = `SELECT ${this.selects.join(', ')} FROM ${this.table}`; // JOINs this.joins.forEach(join => { sql += ` ${join.type} JOIN ${join.table} ON ${join.on}`; }); // WHERE clauses if (this.wheres.length > 0) { const whereConditions = this.wheres.map((where, index) => { params.push(where.value); return `${where.column} ${where.operator} $${params.length}`; }); sql += ` WHERE ${whereConditions.join(' AND ')}`; } // ORDER BY if (this.orderBy) { sql += ` ORDER BY ${this.orderBy.column} ${this.orderBy.direction}`; } // LIMIT and OFFSET if (this.limit) { sql += ` LIMIT $${params.length + 1}`; params.push(this.limit); } if (this.offset) { sql += ` OFFSET $${params.length + 1}`; params.push(this.offset); } return { sql, params }; } } // Usage const query = new SqlQueryBuilder() .from('users') .select(['id', 'name', 'email']) .where('status', '=', 'active') .where('created_at', '>', new Date('2024-01-01')) .join('LEFT', 'orders', 'users.id = orders.user_id') .orderBy('created_at', 'DESC') .limit(10) .offset(0) .build(); console.log(query.sql); // SELECT id, name, email FROM users LEFT JOIN orders ON users.id = orders.user_id WHERE status = $1 AND created_at > $2 ORDER BY created_at DESC LIMIT $3 OFFSET $4

5) Prototype (Creational)

Create new objects by cloning existing ones.

Use: copy configured instancesPros: cheap duplication
Example
// === Node.js TypeScript ===
interface NotificationTemplate {
  id: string;
  type: 'email' | 'sms' | 'push';
  subject?: string;
  body: string;
  variables: Record;
  metadata: {
    priority: number;
    retryCount: number;
    tags: string[];
    createdAt: Date;
  };
}

class NotificationTemplate implements NotificationTemplate {
  id: string;
  type: 'email' | 'sms' | 'push';
  subject?: string;
  body: string;
  variables: Record;
  metadata: {
    priority: number;
    retryCount: number;
    tags: string[];
    createdAt: Date;
  };

  constructor(config: Partial & { type: 'email' | 'sms' | 'push'; body: string }) {
    this.id = config.id || `template-${Date.now()}`;
    this.type = config.type;
    this.subject = config.subject;
    this.body = config.body;
    this.variables = config.variables || {};
    this.metadata = {
      priority: config.metadata?.priority || 0,
      retryCount: config.metadata?.retryCount || 0,
      tags: config.metadata?.tags || [],
      createdAt: config.metadata?.createdAt || new Date()
    };
  }

  // Prototype method - deep clone the template
  clone(): NotificationTemplate {
    // Deep clone including nested objects and dates
    const cloned = Object.create(Object.getPrototypeOf(this));
    cloned.id = `${this.id}-clone-${Date.now()}`;
    cloned.type = this.type;
    cloned.subject = this.subject;
    cloned.body = this.body;
    cloned.variables = { ...this.variables };
    cloned.metadata = {
      priority: this.metadata.priority,
      retryCount: 0, // Reset retry count for new clone
      tags: [...this.metadata.tags],
      createdAt: new Date() // New creation date
    };
    return cloned;
  }

  // Create a customized version from the prototype
  customize(customizations: Partial): NotificationTemplate {
    const customized = this.clone();
    if (customizations.subject !== undefined) customized.subject = customizations.subject;
    if (customizations.body !== undefined) customized.body = customizations.body;
    if (customizations.variables) {
      customized.variables = { ...customized.variables, ...customizations.variables };
    }
    if (customizations.metadata) {
      customized.metadata = { ...customized.metadata, ...customizations.metadata };
    }
    return customized;
  }

  // Render template with variables
  render(vars?: Record): string {
    let rendered = this.body;
    const allVars = { ...this.variables, ...vars };
    
    Object.entries(allVars).forEach(([key, value]) => {
      rendered = rendered.replace(new RegExp(`\\{\\{${key}\\}\\}`, 'g'), value);
    });
    
    return rendered;
  }
}

// Usage: Create base template (expensive configuration)
const baseEmailTemplate = new NotificationTemplate({
  type: 'email',
  subject: 'Order Update',
  body: 'Hello {{name}}, your order #{{orderId}} is now {{status}}.',
  variables: { company: 'MyShop', supportEmail: 'support@myshop.com' },
  metadata: {
    priority: 1,
    retryCount: 0,
    tags: ['order', 'notification'],
    createdAt: new Date()
  }
});

// Clone and customize for different scenarios (cheap operation)
const shippingNotification = baseEmailTemplate.customize({
  subject: 'Your Order Has Shipped!',
  variables: { status: 'shipped' }
});

const deliveredNotification = baseEmailTemplate.customize({
  subject: 'Order Delivered',
  variables: { status: 'delivered' }
});

// Use the cloned templates
console.log(shippingNotification.render({ name: 'John', orderId: '12345' }));
// Output: Hello John, your order #12345 is now shipped.

// Registry pattern with prototypes
class TemplateRegistry {
  private templates = new Map();

  register(name: string, template: NotificationTemplate): void {
    this.templates.set(name, template);
  }

  getPrototype(name: string): NotificationTemplate | undefined {
    return this.templates.get(name);
  }

  createFromPrototype(name: string, customizations?: Partial): NotificationTemplate {
    const prototype = this.templates.get(name);
    if (!prototype) {
      throw new Error(`Template prototype "${name}" not found`);
    }
    return customizations ? prototype.customize(customizations) : prototype.clone();
  }
}

// Setup registry with prototypes
const registry = new TemplateRegistry();
registry.register('order-email', baseEmailTemplate);
registry.register('welcome-email', new NotificationTemplate({
  type: 'email',
  subject: 'Welcome to MyShop!',
  body: 'Welcome {{name}}! Get {{discount}}% off your first order.',
  metadata: { priority: 2, retryCount: 0, tags: ['welcome'], createdAt: new Date() }
}));

// Create instances from prototypes
const order1 = registry.createFromPrototype('order-email', {
  variables: { orderId: '12345', status: 'processing' }
});

const order2 = registry.createFromPrototype('order-email', {
  variables: { orderId: '67890', status: 'shipped' }
});

// === NestJS ===
import { Injectable } from '@nestjs/common';

@Injectable()
export class NotificationTemplate {
  // Same implementation as above
}

@Injectable()
export class TemplateRegistry {
  // Same implementation as above
}

@Injectable()
export class NotificationService {
  constructor(private templateRegistry: TemplateRegistry) {}

  async sendOrderNotification(orderId: string, userId: string, status: string): Promise {
    const template = this.templateRegistry.createFromPrototype('order-email', {
      variables: { orderId, status }
    });
    
    const user = await this.getUser(userId);
    const rendered = template.render({ name: user.name, orderId, status });
    
    // Send notification
    await this.sendEmail(user.email, template.subject || '', rendered);
  }

  private async getUser(userId: string): Promise<{ name: string; email: string }> {
    // Fetch user from database
    return { name: 'John Doe', email: 'john@example.com' };
  }

  private async sendEmail(to: string, subject: string, body: string): Promise {
    // Send email implementation
  }
}

6) Adapter (Structural)

Convert one interface into another clients expect.

Use: integrate third-partyPros: decouple client
Example
// === Node.js TypeScript ===
interface FileUploadResult {
  url: string;
  key: string;
  size: number;
  contentType: string;
  uploadedAt: Date;
}

interface IFileStorage {
  upload(file: Buffer, filename: string, options?: { contentType?: string; folder?: string }): Promise;
  delete(key: string): Promise;
  getUrl(key: string, expiresIn?: number): Promise;
  exists(key: string): Promise;
}

// Legacy AWS S3 SDK v2 (different API)
import { S3 as LegacyS3 } from 'aws-sdk';

class LegacyS3Service {
  private s3: LegacyS3;

  constructor() {
    this.s3 = new LegacyS3({
      accessKeyId: process.env.AWS_ACCESS_KEY_ID,
      secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY,
      region: process.env.AWS_REGION || 'us-east-1'
    });
  }

  // Old API: different method names and return format
  putObject(params: {
    Bucket: string;
    Key: string;
    Body: Buffer;
    ContentType?: string;
  }): Promise<{ Location: string; ETag: string }> {
    return new Promise((resolve, reject) => {
      this.s3.putObject(params, (err, data) => {
        if (err) reject(err);
        else resolve({
          Location: `https://${params.Bucket}.s3.amazonaws.com/${params.Key}`,
          ETag: data.ETag || ''
        });
      });
    });
  }

  deleteObject(params: { Bucket: string; Key: string }): Promise {
    return new Promise((resolve, reject) => {
      this.s3.deleteObject(params, (err) => {
        if (err) reject(err);
        else resolve();
      });
    });
  }

  getSignedUrl(params: { Bucket: string; Key: string; Expires: number }): Promise {
    return new Promise((resolve, reject) => {
      this.s3.getSignedUrl('getObject', params, (err, url) => {
        if (err) reject(err);
        else resolve(url);
      });
    });
  }
}

// Adapter: Wraps legacy S3 to match our IFileStorage interface
class S3StorageAdapter implements IFileStorage {
  private bucket: string;
  private legacyS3: LegacyS3Service;

  constructor(bucket: string, legacyS3?: LegacyS3Service) {
    this.bucket = bucket;
    this.legacyS3 = legacyS3 || new LegacyS3Service();
  }

  async upload(
    file: Buffer,
    filename: string,
    options?: { contentType?: string; folder?: string }
  ): Promise {
    try {
      const key = options?.folder ? `${options.folder}/${filename}` : filename;
      
      const result = await this.legacyS3.putObject({
        Bucket: this.bucket,
        Key: key,
        Body: file,
        ContentType: options?.contentType || 'application/octet-stream'
      });

      return {
        url: result.Location,
        key: key,
        size: file.length,
        contentType: options?.contentType || 'application/octet-stream',
        uploadedAt: new Date()
      };
    } catch (error) {
      throw new Error(`Failed to upload file: ${error.message}`);
    }
  }

  async delete(key: string): Promise {
    try {
      await this.legacyS3.deleteObject({
        Bucket: this.bucket,
        Key: key
      });
    } catch (error) {
      throw new Error(`Failed to delete file: ${error.message}`);
    }
  }

  async getUrl(key: string, expiresIn: number = 3600): Promise {
    try {
      return await this.legacyS3.getSignedUrl({
        Bucket: this.bucket,
        Key: key,
        Expires: expiresIn
      });
    } catch (error) {
      throw new Error(`Failed to get file URL: ${error.message}`);
    }
  }

  async exists(key: string): Promise {
    // Legacy S3 doesn't have direct exists check, would need headObject
    // This is simplified
    try {
      await this.getUrl(key, 1);
      return true;
    } catch {
      return false;
    }
  }
}

// Another adapter for local filesystem (for testing/development)
import * as fs from 'fs/promises';
import * as path from 'path';

class LocalStorageAdapter implements IFileStorage {
  private baseDir: string;

  constructor(baseDir: string = './uploads') {
    this.baseDir = baseDir;
  }

  async upload(
    file: Buffer,
    filename: string,
    options?: { contentType?: string; folder?: string }
  ): Promise {
    try {
      const dir = options?.folder ? path.join(this.baseDir, options.folder) : this.baseDir;
      await fs.mkdir(dir, { recursive: true });
      
      const filePath = path.join(dir, filename);
      await fs.writeFile(filePath, file);

      return {
        url: `/uploads/${options?.folder ? options.folder + '/' : ''}${filename}`,
        key: filePath,
        size: file.length,
        contentType: options?.contentType || 'application/octet-stream',
        uploadedAt: new Date()
      };
    } catch (error) {
      throw new Error(`Failed to upload file locally: ${error.message}`);
    }
  }

  async delete(key: string): Promise {
    try {
      await fs.unlink(key);
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code !== 'ENOENT') {
        throw new Error(`Failed to delete file: ${error.message}`);
      }
    }
  }

  async getUrl(key: string): Promise {
    return `/files/${path.relative(this.baseDir, key)}`;
  }

  async exists(key: string): Promise {
    try {
      await fs.access(key);
      return true;
    } catch {
      return false;
    }
  }
}

// Usage: Client code doesn't need to know which storage implementation
class FileUploadService {
  constructor(private storage: IFileStorage) {}

  async uploadAvatar(userId: string, imageBuffer: Buffer, filename: string): Promise {
    const validImageTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp'];
    const contentType = this.detectContentType(filename);
    
    if (!validImageTypes.includes(contentType)) {
      throw new Error('Invalid image type. Allowed: JPEG, PNG, GIF, WebP');
    }

    if (imageBuffer.length > 5 * 1024 * 1024) { // 5MB limit
      throw new Error('File size exceeds 5MB limit');
    }

    return await this.storage.upload(imageBuffer, filename, {
      contentType,
      folder: `avatars/${userId}`
    });
  }

  private detectContentType(filename: string): string {
    const ext = path.extname(filename).toLowerCase();
    const types: Record = {
      '.jpg': 'image/jpeg',
      '.jpeg': 'image/jpeg',
      '.png': 'image/png',
      '.gif': 'image/gif',
      '.webp': 'image/webp'
    };
    return types[ext] || 'application/octet-stream';
  }
}

// Switch implementations easily
const storage = process.env.NODE_ENV === 'production'
  ? new S3StorageAdapter(process.env.S3_BUCKET || 'my-bucket')
  : new LocalStorageAdapter('./local-uploads');

const uploadService = new FileUploadService(storage);

// === NestJS ===
import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class S3StorageAdapter implements IFileStorage {
  // Same implementation as above
}

@Injectable()
export class LocalStorageAdapter implements IFileStorage {
  // Same implementation as above
}

const FILE_STORAGE_PROVIDER = {
  provide: 'FILE_STORAGE',
  useFactory: (): IFileStorage => {
    return process.env.NODE_ENV === 'production'
      ? new S3StorageAdapter(process.env.S3_BUCKET)
      : new LocalStorageAdapter('./local-uploads');
  }
};

@Injectable()
export class FileUploadService {
  constructor(@Inject('FILE_STORAGE') private storage: IFileStorage) {
    // Same implementation as above
  }
}

7) Facade (Structural)

Provide a simple interface to a complex subsystem.

Use: simplify APIsPros: easier onboarding
Example
// === Node.js TypeScript ===
// Complex subsystems - typically would be in separate modules
class AuthenticationService {
  async verifyToken(token: string): Promise<{ userId: string; email: string } | null> {
    // JWT verification logic
    try {
      const decoded = this.decodeJWT(token);
      if (!decoded || decoded.exp < Date.now() / 1000) {
        return null;
      }
      return { userId: decoded.userId, email: decoded.email };
    } catch {
      return null;
    }
  }

  async checkPermission(userId: string, resource: string, action: string): Promise {
    // Complex permission checking logic
    return true; // Simplified
  }

  private decodeJWT(token: string): any {
    // JWT decoding implementation
    return { userId: 'u1', email: 'user@example.com', exp: Date.now() / 1000 + 3600 };
  }
}

class UserRepository {
  async findById(id: string): Promise {
    // Database query
    return { id, email: 'user@example.com', name: 'John Doe', createdAt: new Date() };
  }

  async updateLastLogin(id: string): Promise {
    // Update last login timestamp
  }
}

class OrderRepository {
  async findByUserId(userId: string): Promise {
    // Database query with joins
    return [
      { id: 'o1', userId, total: 99.99, status: 'completed', createdAt: new Date() },
      { id: 'o2', userId, total: 149.99, status: 'pending', createdAt: new Date() }
    ];
  }

  async getTotalSpent(userId: string): Promise {
    // Aggregate query
    return 249.98;
  }
}

class PaymentService {
  async getPaymentMethods(userId: string): Promise {
    // External API call or database query
    return [
      { id: 'pm1', type: 'card', last4: '4242', brand: 'visa' }
    ];
  }

  async getTransactions(userId: string, limit: number = 10): Promise {
    // Complex query with pagination
    return [];
  }
}

class NotificationService {
  async getUnreadCount(userId: string): Promise {
    // Database count query
    return 3;
  }

  async getRecent(userId: string, limit: number = 5): Promise {
    // Query with sorting and limit
    return [];
  }
}

// Facade: Simplifies complex operations across multiple services
class UserAccountFacade {
  constructor(
    private authService = new AuthenticationService(),
    private userRepo = new UserRepository(),
    private orderRepo = new OrderRepository(),
    private paymentService = new PaymentService(),
    private notificationService = new NotificationService()
  ) {}

  // Single method that orchestrates multiple service calls
  async getAccountDashboard(token: string): Promise<{
    user: any;
    orders: any[];
    stats: { totalSpent: number; orderCount: number; unreadNotifications: number };
    paymentMethods: any[];
  }> {
    // Verify authentication
    const auth = await this.authService.verifyToken(token);
    if (!auth) {
      throw new Error('Invalid or expired token');
    }

    // Check permissions
    const hasAccess = await this.authService.checkPermission(auth.userId, 'account', 'read');
    if (!hasAccess) {
      throw new Error('Insufficient permissions');
    }

    // Update last login
    await this.userRepo.updateLastLogin(auth.userId);

    // Fetch all required data in parallel
    const [user, orders, totalSpent, paymentMethods, unreadCount] = await Promise.all([
      this.userRepo.findById(auth.userId),
      this.orderRepo.findByUserId(auth.userId),
      this.orderRepo.getTotalSpent(auth.userId),
      this.paymentService.getPaymentMethods(auth.userId),
      this.notificationService.getUnreadCount(auth.userId)
    ]);

    return {
      user,
      orders,
      stats: {
        totalSpent,
        orderCount: orders.length,
        unreadNotifications: unreadCount
      },
      paymentMethods
    };
  }

  // Another simplified operation
  async completeProfileSetup(userId: string, profileData: any): Promise {
    // Complex orchestration: validate, update user, send welcome email, create audit log
    await this.userRepo.update(userId, profileData);
    // Additional operations would go here
  }
}

// Usage: Client code only needs to know about the facade
const facade = new UserAccountFacade();
try {
  const dashboard = await facade.getAccountDashboard('bearer-token-here');
  console.log(dashboard);
} catch (error) {
  console.error('Failed to load dashboard:', error.message);
}

// === NestJS ===
import { Injectable, UnauthorizedException, ForbiddenException } from '@nestjs/common';

@Injectable()
export class AuthenticationService {
  // Same implementation
}

@Injectable()
export class UserRepository {
  // Same implementation
}

@Injectable()
export class OrderRepository {
  // Same implementation
}

@Injectable()
export class PaymentService {
  // Same implementation
}

@Injectable()
export class NotificationService {
  // Same implementation
}

@Injectable()
export class UserAccountFacade {
  constructor(
    private authService: AuthenticationService,
    private userRepo: UserRepository,
    private orderRepo: OrderRepository,
    private paymentService: PaymentService,
    private notificationService: NotificationService
  ) {}

  async getAccountDashboard(token: string) {
    const auth = await this.authService.verifyToken(token);
    if (!auth) {
      throw new UnauthorizedException('Invalid or expired token');
    }

    const hasAccess = await this.authService.checkPermission(auth.userId, 'account', 'read');
    if (!hasAccess) {
      throw new ForbiddenException('Insufficient permissions');
    }

    // Rest of implementation same as above
    const [user, orders, totalSpent, paymentMethods, unreadCount] = await Promise.all([
      this.userRepo.findById(auth.userId),
      this.orderRepo.findByUserId(auth.userId),
      this.orderRepo.getTotalSpent(auth.userId),
      this.paymentService.getPaymentMethods(auth.userId),
      this.notificationService.getUnreadCount(auth.userId)
    ]);

    return {
      user,
      orders,
      stats: {
        totalSpent,
        orderCount: orders.length,
        unreadNotifications: unreadCount
      },
      paymentMethods
    };
  }
}

@Controller('account')
export class AccountController {
  constructor(private accountFacade: UserAccountFacade) {}

  @Get('dashboard')
  async getDashboard(@Headers('authorization') token: string) {
    return this.accountFacade.getAccountDashboard(token);
  }
}

8) Proxy (Structural)

Control access to another object (cache, lazy, auth).

Use: caching/permissionPros: performance, safety
Example
// === Node.js TypeScript ===
interface CacheEntry {
  data: T;
  expiresAt: number;
}

class CacheProxy {
  private cache = new Map>();
  private defaultTTL: number;

  constructor(defaultTTLSeconds: number = 300) {
    this.defaultTTL = defaultTTLSeconds * 1000;
  }

  private generateKey(args: any[]): string {
    return JSON.stringify(args);
  }

  private isExpired(entry: CacheEntry): boolean {
    return Date.now() > entry.expiresAt;
  }

  private cleanup(): void {
    for (const [key, entry] of this.cache.entries()) {
      if (this.isExpired(entry)) {
        this.cache.delete(key);
      }
    }
  }

  wrap

( fn: (...args: P) => Promise, ttlSeconds?: number ): (...args: P) => Promise { const ttl = (ttlSeconds || this.defaultTTL / 1000) * 1000; return async (...args: P): Promise => { // Periodic cleanup if (Math.random() < 0.01) { // ~1% of requests trigger cleanup this.cleanup(); } const key = this.generateKey(args); const cached = this.cache.get(key); if (cached && !this.isExpired(cached)) { return cached.data as R; } try { const result = await fn(...args); this.cache.set(key, { data: result as any, expiresAt: Date.now() + ttl }); return result; } catch (error) { // Don't cache errors throw error; } }; } clear(): void { this.cache.clear(); } invalidate(keyPattern?: string): void { if (!keyPattern) { this.cache.clear(); return; } for (const key of this.cache.keys()) { if (key.includes(keyPattern)) { this.cache.delete(key); } } } } // Usage examples class UserService { private cacheProxy = new CacheProxy(300); // 5 minute default TTL // Expensive database query - cached for 5 minutes async getUserById(id: string): Promise { const fetchUser = async (userId: string) => { console.log(`Fetching user ${userId} from database...`); // Simulate database query await new Promise(resolve => setTimeout(resolve, 100)); return { id: userId, name: 'John Doe', email: 'john@example.com', profile: { bio: '...', avatar: '...' } }; }; const cachedFetch = this.cacheProxy.wrap(fetchUser, 300); return cachedFetch(id); } // Expensive calculation - cached for 1 hour async getUserStats(userId: string): Promise { const calculateStats = async (id: string) => { console.log(`Calculating stats for user ${id}...`); // Expensive calculations, aggregations await new Promise(resolve => setTimeout(resolve, 500)); return { totalOrders: 42, totalSpent: 1250.99, averageOrderValue: 29.79 }; }; const cachedCalc = this.cacheProxy.wrap(calculateStats, 3600); return cachedCalc(userId); } // When user updates, invalidate cache async updateUser(userId: string, data: any): Promise { // Update in database await this.saveUser(userId, data); // Invalidate all cached data for this user this.cacheProxy.invalidate(userId); } private async saveUser(userId: string, data: any): Promise { // Database update } } // Lazy loading proxy class LazyDataProxy { private data: T | null = null; private loader: () => Promise; private loading: Promise | null = null; constructor(loader: () => Promise) { this.loader = loader; } async get(): Promise { if (this.data !== null) { return this.data; } if (this.loading) { return this.loading; } this.loading = this.loader().then(result => { this.data = result; this.loading = null; return result; }); return this.loading; } invalidate(): void { this.data = null; this.loading = null; } } // Protection proxy - access control class ProtectedUserService { private userService = new UserService(); async getUserById(userId: string, requesterId: string, requesterRole: string): Promise { // Authorization check if (requesterRole !== 'admin' && requesterId !== userId) { throw new Error('Access denied: You can only view your own profile'); } return this.userService.getUserById(userId); } async deleteUser(userId: string, requesterRole: string): Promise { // Permission check if (requesterRole !== 'admin') { throw new Error('Access denied: Only admins can delete users'); } // Perform deletion await this.userService.deleteUser(userId); } } // === NestJS === import { Injectable, NestInterceptor, ExecutionContext, CallHandler, CACHE_MANAGER, Inject } from '@nestjs/common'; import { Observable, of } from 'rxjs'; import { tap } from 'rxjs/operators'; import { Cache } from 'cache-manager'; @Injectable() export class CacheInterceptor implements NestInterceptor { constructor(@Inject(CACHE_MANAGER) private cacheManager: Cache) {} async intercept(context: ExecutionContext, next: CallHandler): Promise> { const request = context.switchToHttp().getRequest(); const { method, url, params, query, body } = request; // Skip caching for non-GET requests if (method !== 'GET') { return next.handle(); } // Generate cache key const cacheKey = `cache:${method}:${url}:${JSON.stringify({ params, query })}`; // Try to get from cache const cached = await this.cacheManager.get(cacheKey); if (cached) { return of(cached); } // Execute and cache the result return next.handle().pipe( tap(async (data) => { // Cache for 5 minutes await this.cacheManager.set(cacheKey, data, { ttl: 300 }); }) ); } } // Usage in controller @Controller('users') @UseInterceptors(CacheInterceptor) export class UsersController { constructor(private userService: UserService) {} @Get(':id') async getUser(@Param('id') id: string) { return this.userService.getUserById(id); } } // Logging proxy @Injectable() export class LoggingProxy implements NestInterceptor { intercept(context: ExecutionContext, next: CallHandler): Observable { const request = context.switchToHttp().getRequest(); const startTime = Date.now(); const { method, url } = request; console.log(`[${new Date().toISOString()}] ${method} ${url} - Started`); return next.handle().pipe( tap({ next: () => { const duration = Date.now() - startTime; console.log(`[${new Date().toISOString()}] ${method} ${url} - Completed in ${duration}ms`); }, error: (error) => { const duration = Date.now() - startTime; console.error(`[${new Date().toISOString()}] ${method} ${url} - Failed after ${duration}ms:`, error.message); } }) ); } }

9) Decorator (Structural)

Add responsibilities to objects dynamically without subclassing.

Use: optional featuresPros: flexible composition
Example
// === Node.js TypeScript ===
import { performance } from 'perf_hooks';

// Timing decorator with detailed metrics
function LogExecutionTime(operation?: string) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    const methodName = operation || propertyKey;

    descriptor.value = async function(...args: any[]) {
      const start = performance.now();
      const startMemory = process.memoryUsage().heapUsed;

      try {
        const result = await originalMethod.apply(this, args);
        const end = performance.now();
        const endMemory = process.memoryUsage().heapUsed;
        const duration = (end - start).toFixed(2);
        const memoryDelta = ((endMemory - startMemory) / 1024 / 1024).toFixed(2);

        console.log(`[${methodName}] Completed in ${duration}ms (Memory: ${memoryDelta}MB)`);
        return result;
      } catch (error) {
        const end = performance.now();
        const duration = (end - start).toFixed(2);
        console.error(`[${methodName}] Failed after ${duration}ms:`, error.message);
        throw error;
      }
    };

    return descriptor;
  };
}

// Retry decorator
function Retry(maxAttempts: number = 3, delayMs: number = 1000) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = async function(...args: any[]) {
      let lastError: Error;

      for (let attempt = 1; attempt <= maxAttempts; attempt++) {
        try {
          return await originalMethod.apply(this, args);
        } catch (error) {
          lastError = error as Error;
          
          if (attempt < maxAttempts) {
            console.warn(`[${propertyKey}] Attempt ${attempt} failed, retrying in ${delayMs}ms...`);
            await new Promise(resolve => setTimeout(resolve, delayMs));
          }
        }
      }

      throw new Error(`${propertyKey} failed after ${maxAttempts} attempts: ${lastError!.message}`);
    };

    return descriptor;
  };
}

// Cache decorator
function CacheResult(ttlSeconds: number = 300) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;
    const cache = new Map();

    descriptor.value = async function(...args: any[]) {
      const cacheKey = `${propertyKey}:${JSON.stringify(args)}`;
      const cached = cache.get(cacheKey);

      if (cached && cached.expiresAt > Date.now()) {
        return cached.data;
      }

      const result = await originalMethod.apply(this, args);
      cache.set(cacheKey, {
        data: result,
        expiresAt: Date.now() + (ttlSeconds * 1000)
      });

      return result;
    };

    return descriptor;
  };
}

// Validate decorator
function Validate(...validators: Array<(value: any) => boolean | string>) {
  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = async function(...args: any[]) {
      for (const validator of validators) {
        const result = validator(args[0]);
        if (result !== true) {
          const message = typeof result === 'string' ? result : 'Validation failed';
          throw new Error(`[${propertyKey}] ${message}`);
        }
      }
      return originalMethod.apply(this, args);
    };

    return descriptor;
  };
}

// Rate limit decorator
function RateLimit(maxCalls: number, windowMs: number) {
  const callHistory = new Map();

  return function(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    const originalMethod = descriptor.value;

    descriptor.value = async function(...args: any[]) {
      const key = `${target.constructor.name}:${propertyKey}`;
      const now = Date.now();
      const calls = callHistory.get(key) || [];

      // Remove old calls outside the window
      const recentCalls = calls.filter(time => now - time < windowMs);

      if (recentCalls.length >= maxCalls) {
        throw new Error(`Rate limit exceeded for ${propertyKey}. Max ${maxCalls} calls per ${windowMs}ms`);
      }

      recentCalls.push(now);
      callHistory.set(key, recentCalls);

      return originalMethod.apply(this, args);
    };

    return descriptor;
  };
}

// Usage: Combine multiple decorators
class PaymentService {
  @LogExecutionTime('Process Payment')
  @Retry(3, 1000)
  @RateLimit(10, 60000) // Max 10 calls per minute
  @Validate(
    (data: any) => data.amount > 0 || 'Amount must be positive',
    (data: any) => data.currency?.length === 3 || 'Invalid currency code'
  )
  async processPayment(data: { amount: number; currency: string; userId: string }): Promise {
    // Simulate API call that might fail
    if (Math.random() < 0.3) {
      throw new Error('Payment gateway temporarily unavailable');
    }

    await new Promise(resolve => setTimeout(resolve, 100));
    return {
      transactionId: `txn_${Date.now()}`,
      status: 'completed',
      amount: data.amount,
      currency: data.currency
    };
  }

  @CacheResult(600) // Cache for 10 minutes
  @LogExecutionTime('Get User Balance')
  async getUserBalance(userId: string): Promise {
    // Expensive database query
    await new Promise(resolve => setTimeout(resolve, 200));
    return 1250.99;
  }
}

// === NestJS ===
import { Injectable, UseInterceptors, applyDecorators, SetMetadata } from '@nestjs/common';
import { CacheInterceptor, CacheKey, CacheTTL } from '@nestjs/cache-manager';

// Custom decorator combining multiple interceptors
export function CachedApi(ttl: number = 300) {
  return applyDecorators(
    UseInterceptors(CacheInterceptor),
    CacheTTL(ttl),
    SetMetadata('cached', true)
  );
}

export function RateLimited(maxCalls: number, windowMs: number) {
  return SetMetadata('rateLimit', { maxCalls, windowMs });
}

@Injectable()
export class PaymentService {
  @CachedApi(600)
  async getUserBalance(userId: string): Promise {
    // Implementation
    return 1250.99;
  }

  @RateLimited(10, 60000)
  async processPayment(data: any): Promise {
    // Implementation
    return { transactionId: 'txn_123' };
  }
}

// Guard for rate limiting
import { Injectable, CanActivate, ExecutionContext, HttpException, HttpStatus } from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class RateLimitGuard implements CanActivate {
  private requests = new Map();

  constructor(private reflector: Reflector) {}

  canActivate(context: ExecutionContext): boolean {
    const rateLimit = this.reflector.get<{ maxCalls: number; windowMs: number }>('rateLimit', context.getHandler());
    
    if (!rateLimit) {
      return true;
    }

    const request = context.switchToHttp().getRequest();
    const key = `${request.ip}:${context.getHandler().name}`;
    const now = Date.now();
    const calls = this.requests.get(key) || [];

    const recentCalls = calls.filter(time => now - time < rateLimit.windowMs);

    if (recentCalls.length >= rateLimit.maxCalls) {
      throw new HttpException('Rate limit exceeded', HttpStatus.TOO_MANY_REQUESTS);
    }

    recentCalls.push(now);
    this.requests.set(key, recentCalls);

    return true;
  }
}

10) Composite (Structural)

Treat individual objects and compositions uniformly.

Use: trees/menusPros: recursive ops
Example
// === Node.js TypeScript ===
// File system structure using Composite pattern
interface FileSystemNode {
  getName(): string;
  getSize(): number;
  getType(): 'file' | 'directory';
  getPath(): string;
  find(pattern: string): FileSystemNode[];
  accept(visitor: FileSystemVisitor): void;
}

interface FileSystemVisitor {
  visitFile(file: File): void;
  visitDirectory(directory: Directory): void;
}

class File implements FileSystemNode {
  constructor(
    private name: string,
    private size: number,
    private parent: Directory | null = null
  ) {}

  getName(): string {
    return this.name;
  }

  getSize(): number {
    return this.size;
  }

  getType(): 'file' | 'directory' {
    return 'file';
  }

  getPath(): string {
    return this.parent ? `${this.parent.getPath()}/${this.name}` : this.name;
  }

  find(pattern: string): FileSystemNode[] {
    return this.name.includes(pattern) ? [this] : [];
  }

  accept(visitor: FileSystemVisitor): void {
    visitor.visitFile(this);
  }
}

class Directory implements FileSystemNode {
  private children: FileSystemNode[] = [];

  constructor(
    private name: string,
    private parent: Directory | null = null
  ) {}

  getName(): string {
    return this.name;
  }

  getSize(): number {
    // Composite: sum of all children
    return this.children.reduce((total, child) => total + child.getSize(), 0);
  }

  getType(): 'file' | 'directory' {
    return 'directory';
  }

  getPath(): string {
    return this.parent ? `${this.parent.getPath()}/${this.name}` : this.name;
  }

  add(child: FileSystemNode): void {
    this.children.push(child);
  }

  remove(child: FileSystemNode): void {
    const index = this.children.indexOf(child);
    if (index > -1) {
      this.children.splice(index, 1);
    }
  }

  getChildren(): FileSystemNode[] {
    return [...this.children];
  }

  find(pattern: string): FileSystemNode[] {
    const results: FileSystemNode[] = [];
    
    // Search in current directory name
    if (this.name.includes(pattern)) {
      results.push(this);
    }

    // Recursively search in children
    for (const child of this.children) {
      results.push(...child.find(pattern));
    }

    return results;
  }

  accept(visitor: FileSystemVisitor): void {
    visitor.visitDirectory(this);
    // Visit all children
    for (const child of this.children) {
      child.accept(visitor);
    }
  }
}

// Visitor pattern integration
class FileSizeCalculator implements FileSystemVisitor {
  private totalSize = 0;
  private fileCount = 0;
  private directoryCount = 0;

  visitFile(file: File): void {
    this.totalSize += file.getSize();
    this.fileCount++;
  }

  visitDirectory(directory: Directory): void {
    this.directoryCount++;
  }

  getStats(): { totalSize: number; fileCount: number; directoryCount: number } {
    return {
      totalSize: this.totalSize,
      fileCount: this.fileCount,
      directoryCount: this.directoryCount
    };
  }
}

// Usage
const root = new Directory('root');
const documents = new Directory('documents', root);
const images = new Directory('images', root);

const file1 = new File('readme.txt', 1024, root);
const file2 = new File('report.pdf', 5120, documents);
const file3 = new File('photo.jpg', 2048, images);
const file4 = new File('photo2.jpg', 3072, images);

root.add(file1);
root.add(documents);
root.add(images);
documents.add(file2);
images.add(file3);
images.add(file4);

console.log(`Total size: ${root.getSize()} bytes`); // 11264 bytes
console.log(`Images folder size: ${images.getSize()} bytes`); // 5120 bytes

// Find operation works on both files and directories
const results = root.find('photo');
console.log(`Found ${results.length} items matching 'photo'`);

// Visitor pattern
const calculator = new FileSizeCalculator();
root.accept(calculator);
const stats = calculator.getStats();
console.log('Stats:', stats);

// === Another Example: Permission System ===
interface PermissionNode {
  hasPermission(permission: string): boolean;
  addPermission(permission: string): void;
  removePermission(permission: string): void;
  getAllPermissions(): string[];
}

class Permission implements PermissionNode {
  private permissions: Set = new Set();

  hasPermission(permission: string): boolean {
    return this.permissions.has(permission);
  }

  addPermission(permission: string): void {
    this.permissions.add(permission);
  }

  removePermission(permission: string): void {
    this.permissions.delete(permission);
  }

  getAllPermissions(): string[] {
    return Array.from(this.permissions);
  }
}

class PermissionGroup implements PermissionNode {
  private permissions: Set = new Set();
  private children: PermissionNode[] = [];

  constructor(private name: string) {}

  add(node: PermissionNode): void {
    this.children.push(node);
  }

  remove(node: PermissionNode): void {
    const index = this.children.indexOf(node);
    if (index > -1) {
      this.children.splice(index, 1);
    }
  }

  hasPermission(permission: string): boolean {
    // Check own permissions first
    if (this.permissions.has(permission)) {
      return true;
    }
    
    // Check children recursively
    return this.children.some(child => child.hasPermission(permission));
  }

  addPermission(permission: string): void {
    this.permissions.add(permission);
  }

  removePermission(permission: string): void {
    this.permissions.delete(permission);
    // Optionally remove from children
    this.children.forEach(child => child.removePermission(permission));
  }

  getAllPermissions(): string[] {
    const ownPerms = Array.from(this.permissions);
    const childPerms = this.children.flatMap(child => child.getAllPermissions());
    
    // Merge and deduplicate
    return Array.from(new Set([...ownPerms, ...childPerms]));
  }
}

// Usage
const adminGroup = new PermissionGroup('Admin');
adminGroup.addPermission('users.read');
adminGroup.addPermission('users.write');

const userPermissions = new Permission();
userPermissions.addPermission('profile.read');
userPermissions.addPermission('profile.write');

adminGroup.add(userPermissions);

console.log(adminGroup.hasPermission('users.write')); // true
console.log(adminGroup.hasPermission('profile.read')); // true (from child)
console.log(adminGroup.getAllPermissions()); // ['users.read', 'users.write', 'profile.read', 'profile.write']

// === NestJS ===
import { Injectable } from '@nestjs/common';

@Injectable()
export class FileSystemService {
  createDirectory(name: string, parent?: Directory): Directory {
    return new Directory(name, parent || null);
  }

  createFile(name: string, size: number, parent: Directory): File {
    const file = new File(name, size, parent);
    parent.add(file);
    return file;
  }

  calculateDirectorySize(directory: Directory): number {
    return directory.getSize();
  }
}

11) Strategy (Behavioral)

Define a family of algorithms; make them interchangeable.

Use: pluggable behaviorPros: open/closed
Example
// === Node.js TypeScript ===
interface PaymentResult {
  success: boolean;
  transactionId?: string;
  error?: string;
  gateway?: string;
}

interface PaymentStrategy {
  processPayment(amount: number, currency: string, metadata: Record): Promise;
  refund(transactionId: string, amount: number): Promise;
  validate(amount: number, metadata: Record): boolean;
}

// Credit Card Strategy
class CreditCardStrategy implements PaymentStrategy {
  constructor(
    private apiKey: string,
    private merchantId: string
  ) {}

  async processPayment(amount: number, currency: string, metadata: Record): Promise {
    try {
      if (!this.validate(amount, metadata)) {
        return { success: false, error: 'Invalid payment data', gateway: 'creditcard' };
      }

      // Simulate API call to payment gateway
      const response = await this.callPaymentGateway({
        amount,
        currency,
        cardNumber: metadata.cardNumber,
        cvv: metadata.cvv,
        expiryDate: metadata.expiryDate,
        cardholderName: metadata.cardholderName
      });

      return {
        success: response.status === 'approved',
        transactionId: response.transactionId,
        gateway: 'creditcard'
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        gateway: 'creditcard'
      };
    }
  }

  async refund(transactionId: string, amount: number): Promise {
    try {
      const response = await this.callRefundGateway(transactionId, amount);
      return {
        success: response.status === 'refunded',
        transactionId: response.refundId,
        gateway: 'creditcard'
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        gateway: 'creditcard'
      };
    }
  }

  validate(amount: number, metadata: Record): boolean {
    if (amount <= 0) return false;
    if (!metadata.cardNumber || !this.isValidCardNumber(metadata.cardNumber)) return false;
    if (!metadata.expiryDate || !this.isValidExpiryDate(metadata.expiryDate)) return false;
    if (!metadata.cvv || metadata.cvv.length !== 3) return false;
    return true;
  }

  private async callPaymentGateway(data: any): Promise {
    // Simulate API call
    await new Promise(resolve => setTimeout(resolve, 100));
    return {
      status: 'approved',
      transactionId: `txn_cc_${Date.now()}`
    };
  }

  private async callRefundGateway(transactionId: string, amount: number): Promise {
    await new Promise(resolve => setTimeout(resolve, 100));
    return {
      status: 'refunded',
      refundId: `refund_${Date.now()}`
    };
  }

  private isValidCardNumber(cardNumber: string): boolean {
    return /^\d{13,19}$/.test(cardNumber.replace(/\s/g, ''));
  }

  private isValidExpiryDate(expiryDate: string): boolean {
    const [month, year] = expiryDate.split('/').map(Number);
    const now = new Date();
    const expiry = new Date(2000 + year, month - 1);
    return expiry > now;
  }
}

// PayPal Strategy
class PayPalStrategy implements PaymentStrategy {
  constructor(
    private clientId: string,
    private clientSecret: string
  ) {}

  async processPayment(amount: number, currency: string, metadata: Record): Promise {
    try {
      if (!this.validate(amount, metadata)) {
        return { success: false, error: 'Invalid payment data', gateway: 'paypal' };
      }

      const accessToken = await this.getAccessToken();
      const response = await this.createPayment(accessToken, amount, currency, metadata.paypalEmail);

      return {
        success: response.state === 'approved',
        transactionId: response.id,
        gateway: 'paypal'
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        gateway: 'paypal'
      };
    }
  }

  async refund(transactionId: string, amount: number): Promise {
    try {
      const accessToken = await this.getAccessToken();
      const response = await this.refundPayment(accessToken, transactionId, amount);
      
      return {
        success: response.state === 'completed',
        transactionId: response.id,
        gateway: 'paypal'
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        gateway: 'paypal'
      };
    }
  }

  validate(amount: number, metadata: Record): boolean {
    if (amount <= 0) return false;
    if (!metadata.paypalEmail || !this.isValidEmail(metadata.paypalEmail)) return false;
    return true;
  }

  private async getAccessToken(): Promise {
    await new Promise(resolve => setTimeout(resolve, 50));
    return `paypal_token_${Date.now()}`;
  }

  private async createPayment(token: string, amount: number, currency: string, email: string): Promise {
    await new Promise(resolve => setTimeout(resolve, 150));
    return {
      id: `paypal_${Date.now()}`,
      state: 'approved'
    };
  }

  private async refundPayment(token: string, transactionId: string, amount: number): Promise {
    await new Promise(resolve => setTimeout(resolve, 150));
    return {
      id: `refund_${Date.now()}`,
      state: 'completed'
    };
  }

  private isValidEmail(email: string): boolean {
    return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email);
  }
}

// Cryptocurrency Strategy
class CryptoStrategy implements PaymentStrategy {
  constructor(private walletAddress: string) {}

  async processPayment(amount: number, currency: string, metadata: Record): Promise {
    try {
      if (!this.validate(amount, metadata)) {
        return { success: false, error: 'Invalid payment data', gateway: 'crypto' };
      }

      const transactionHash = await this.initiateBlockchainTransaction(
        metadata.fromAddress,
        this.walletAddress,
        amount,
        currency
      );

      return {
        success: true,
        transactionId: transactionHash,
        gateway: 'crypto'
      };
    } catch (error) {
      return {
        success: false,
        error: error.message,
        gateway: 'crypto'
      };
    }
  }

  async refund(transactionId: string, amount: number): Promise {
    // Crypto refunds typically require a reverse transaction
    return {
      success: false,
      error: 'Crypto refunds must be processed manually',
      gateway: 'crypto'
    };
  }

  validate(amount: number, metadata: Record): boolean {
    if (amount <= 0) return false;
    if (!metadata.fromAddress || !this.isValidAddress(metadata.fromAddress)) return false;
    return true;
  }

  private async initiateBlockchainTransaction(from: string, to: string, amount: number, currency: string): Promise {
    await new Promise(resolve => setTimeout(resolve, 200));
    return `0x${Math.random().toString(16).substr(2, 64)}`;
  }

  private isValidAddress(address: string): boolean {
    return /^0x[a-fA-F0-9]{40}$/.test(address);
  }
}

// Payment Service - Uses Strategy Pattern
class PaymentService {
  private strategies: Map = new Map();

  constructor() {
    // Register default strategies
    this.registerStrategy('creditcard', new CreditCardStrategy(
      process.env.CC_API_KEY || '',
      process.env.CC_MERCHANT_ID || ''
    ));
    this.registerStrategy('paypal', new PayPalStrategy(
      process.env.PAYPAL_CLIENT_ID || '',
      process.env.PAYPAL_CLIENT_SECRET || ''
    ));
    this.registerStrategy('crypto', new CryptoStrategy(
      process.env.CRYPTO_WALLET || ''
    ));
  }

  registerStrategy(name: string, strategy: PaymentStrategy): void {
    this.strategies.set(name, strategy);
  }

  async processPayment(
    method: string,
    amount: number,
    currency: string,
    metadata: Record
  ): Promise {
    const strategy = this.strategies.get(method);
    if (!strategy) {
      return {
        success: false,
        error: `Payment method '${method}' not supported`
      };
    }

    return strategy.processPayment(amount, currency, metadata);
  }

  async refund(method: string, transactionId: string, amount: number): Promise {
    const strategy = this.strategies.get(method);
    if (!strategy) {
      return {
        success: false,
        error: `Payment method '${method}' not supported`
      };
    }

    return strategy.refund(transactionId, amount);
  }
}

// Usage
const paymentService = new PaymentService();

// Process payment with different strategies
const creditCardResult = await paymentService.processPayment('creditcard', 99.99, 'USD', {
  cardNumber: '4111111111111111',
  cvv: '123',
  expiryDate: '12/25',
  cardholderName: 'John Doe'
});

const paypalResult = await paymentService.processPayment('paypal', 99.99, 'USD', {
  paypalEmail: 'user@example.com'
});

// === NestJS ===
import { Injectable, Inject } from '@nestjs/common';

@Injectable()
export class CreditCardStrategy implements PaymentStrategy {
  // Same implementation as above
}

@Injectable()
export class PayPalStrategy implements PaymentStrategy {
  // Same implementation as above
}

@Injectable()
export class PaymentService {
  private strategies: Map = new Map();

  constructor(
    @Inject('PAYMENT_STRATEGIES') strategies: Array<{ name: string; strategy: PaymentStrategy }>
  ) {
    strategies.forEach(({ name, strategy }) => {
      this.strategies.set(name, strategy);
    });
  }

  async processPayment(method: string, amount: number, currency: string, metadata: any): Promise {
    const strategy = this.strategies.get(method);
    if (!strategy) {
      throw new Error(`Payment method '${method}' not supported`);
    }
    return strategy.processPayment(amount, currency, metadata);
  }
}

// Module configuration
@Module({
  providers: [
    CreditCardStrategy,
    PayPalStrategy,
    {
      provide: 'PAYMENT_STRATEGIES',
      useFactory: (cc: CreditCardStrategy, pp: PayPalStrategy) => [
        { name: 'creditcard', strategy: cc },
        { name: 'paypal', strategy: pp }
      ],
      inject: [CreditCardStrategy, PayPalStrategy]
    },
    PaymentService
  ],
  exports: [PaymentService]
})
export class PaymentModule {}

12) Observer (Behavioral)

One-to-many dependency: observers get notified of subject changes.

Use: events/reactivityPros: decoupled updates
Example
// === Node.js TypeScript ===
import { EventEmitter } from 'events';

// Event types
interface UserCreatedEvent {
  userId: string;
  email: string;
  name: string;
  createdAt: Date;
}

interface OrderCreatedEvent {
  orderId: string;
  userId: string;
  amount: number;
  items: Array<{ productId: string; quantity: number; price: number }>;
  createdAt: Date;
}

interface OrderStatusChangedEvent {
  orderId: string;
  userId: string;
  oldStatus: string;
  newStatus: string;
  changedAt: Date;
}

// Observer implementations
class AuditLogger {
  private logFile: string;

  constructor(logFile: string = './audit.log') {
    this.logFile = logFile;
  }

  logUserCreated(event: UserCreatedEvent): void {
    console.log(`[AUDIT] User created: ${event.userId} (${event.email}) at ${event.createdAt.toISOString()}`);
    // Write to audit log file
  }

  logOrderCreated(event: OrderCreatedEvent): void {
    console.log(`[AUDIT] Order created: ${event.orderId} by user ${event.userId} for $${event.amount}`);
  }

  logOrderStatusChange(event: OrderStatusChangedEvent): void {
    console.log(`[AUDIT] Order ${event.orderId} status changed: ${event.oldStatus} -> ${event.newStatus}`);
  }
}

class EmailNotificationService {
  async sendWelcomeEmail(event: UserCreatedEvent): Promise {
    console.log(`[EMAIL] Sending welcome email to ${event.email}...`);
    // Send email implementation
    await this.sendEmail(event.email, 'Welcome!', `Hello ${event.name}, welcome to our platform!`);
  }

  async sendOrderConfirmation(event: OrderCreatedEvent): Promise {
    const user = await this.getUser(event.userId);
    console.log(`[EMAIL] Sending order confirmation to ${user.email}...`);
    await this.sendEmail(
      user.email,
      `Order Confirmation #${event.orderId}`,
      `Your order for $${event.amount} has been confirmed.`
    );
  }

  async sendOrderStatusUpdate(event: OrderStatusChangedEvent): Promise {
    const user = await this.getUser(event.userId);
    console.log(`[EMAIL] Sending status update to ${user.email}...`);
    await this.sendEmail(
      user.email,
      `Order #${event.orderId} Status Update`,
      `Your order status has been updated to: ${event.newStatus}`
    );
  }

  private async sendEmail(to: string, subject: string, body: string): Promise {
    // Email sending implementation
    await new Promise(resolve => setTimeout(resolve, 100));
  }

  private async getUser(userId: string): Promise<{ email: string }> {
    // Fetch user from database
    return { email: 'user@example.com' };
  }
}

class InventoryService {
  async updateInventory(event: OrderCreatedEvent): Promise {
    console.log(`[INVENTORY] Updating inventory for order ${event.orderId}...`);
    
    for (const item of event.items) {
      // Decrease stock
      await this.decreaseStock(item.productId, item.quantity);
      console.log(`[INVENTORY] Decreased stock for product ${item.productId} by ${item.quantity}`);
    }
  }

  async restoreInventory(event: OrderStatusChangedEvent): Promise {
    if (event.newStatus === 'cancelled') {
      console.log(`[INVENTORY] Restoring inventory for cancelled order ${event.orderId}...`);
      // Get order details and restore stock
      const order = await this.getOrder(event.orderId);
      for (const item of order.items) {
        await this.increaseStock(item.productId, item.quantity);
      }
    }
  }

  private async decreaseStock(productId: string, quantity: number): Promise {
    // Database update
    await new Promise(resolve => setTimeout(resolve, 50));
  }

  private async increaseStock(productId: string, quantity: number): Promise {
    // Database update
    await new Promise(resolve => setTimeout(resolve, 50));
  }

  private async getOrder(orderId: string): Promise {
    // Fetch order from database
    return {
      orderId,
      userId: 'u1',
      amount: 99.99,
      items: [],
      createdAt: new Date()
    };
  }
}

class AnalyticsService {
  private metrics: Map = new Map();

  trackUserCreated(event: UserCreatedEvent): void {
    this.incrementMetric('users.created');
    this.incrementMetric(`users.created.${new Date(event.createdAt).toISOString().split('T')[0]}`);
    console.log(`[ANALYTICS] Tracked new user registration`);
  }

  trackOrderCreated(event: OrderCreatedEvent): void {
    this.incrementMetric('orders.created');
    this.incrementMetric('revenue.total', event.amount);
    console.log(`[ANALYTICS] Tracked new order: $${event.amount}`);
  }

  trackOrderStatusChange(event: OrderStatusChangedEvent): void {
    this.incrementMetric(`orders.status.${event.newStatus}`);
    console.log(`[ANALYTICS] Tracked order status change: ${event.newStatus}`);
  }

  private incrementMetric(metric: string, value: number = 1): void {
    this.metrics.set(metric, (this.metrics.get(metric) || 0) + value);
  }

  getMetric(metric: string): number {
    return this.metrics.get(metric) || 0;
  }
}

// Event Bus / Subject
class EventBus extends EventEmitter {
  private auditLogger = new AuditLogger();
  private emailService = new EmailNotificationService();
  private inventoryService = new InventoryService();
  private analyticsService = new AnalyticsService();

  constructor() {
    super();

    // Register all observers
    this.on('user.created', (event: UserCreatedEvent) => {
      this.auditLogger.logUserCreated(event);
      this.emailService.sendWelcomeEmail(event).catch(console.error);
      this.analyticsService.trackUserCreated(event);
    });

    this.on('order.created', (event: OrderCreatedEvent) => {
      this.auditLogger.logOrderCreated(event);
      this.emailService.sendOrderConfirmation(event).catch(console.error);
      this.inventoryService.updateInventory(event).catch(console.error);
      this.analyticsService.trackOrderCreated(event);
    });

    this.on('order.status.changed', (event: OrderStatusChangedEvent) => {
      this.auditLogger.logOrderStatusChange(event);
      this.emailService.sendOrderStatusUpdate(event).catch(console.error);
      this.inventoryService.restoreInventory(event).catch(console.error);
      this.analyticsService.trackOrderStatusChange(event);
    });
  }

  // Type-safe event emission
  emitUserCreated(event: UserCreatedEvent): void {
    this.emit('user.created', event);
  }

  emitOrderCreated(event: OrderCreatedEvent): void {
    this.emit('order.created', event);
  }

  emitOrderStatusChanged(event: OrderStatusChangedEvent): void {
    this.emit('order.status.changed', event);
  }
}

// Usage
const eventBus = new EventBus();

// Services emit events
class UserService {
  constructor(private eventBus: EventBus) {}

  async createUser(email: string, name: string): Promise {
    const userId = `user_${Date.now()}`;
    
    // Create user in database
    await this.saveUserToDatabase(userId, email, name);

    // Emit event - all observers will be notified
    this.eventBus.emitUserCreated({
      userId,
      email,
      name,
      createdAt: new Date()
    });

    return userId;
  }

  private async saveUserToDatabase(userId: string, email: string, name: string): Promise {
    // Database save
    await new Promise(resolve => setTimeout(resolve, 50));
  }
}

class OrderService {
  constructor(private eventBus: EventBus) {}

  async createOrder(userId: string, items: Array<{ productId: string; quantity: number; price: number }>): Promise {
    const orderId = `order_${Date.now()}`;
    const amount = items.reduce((sum, item) => sum + item.price * item.quantity, 0);

    // Create order in database
    await this.saveOrderToDatabase(orderId, userId, amount, items);

    // Emit event
    this.eventBus.emitOrderCreated({
      orderId,
      userId,
      amount,
      items,
      createdAt: new Date()
    });

    return orderId;
  }

  async updateOrderStatus(orderId: string, userId: string, newStatus: string): Promise {
    const oldStatus = await this.getOrderStatus(orderId);

    // Update in database
    await this.updateOrderStatusInDatabase(orderId, newStatus);

    // Emit event
    this.eventBus.emitOrderStatusChanged({
      orderId,
      userId,
      oldStatus,
      newStatus,
      changedAt: new Date()
    });
  }

  private async saveOrderToDatabase(orderId: string, userId: string, amount: number, items: any[]): Promise {
    await new Promise(resolve => setTimeout(resolve, 50));
  }

  private async getOrderStatus(orderId: string): Promise {
    return 'pending';
  }

  private async updateOrderStatusInDatabase(orderId: string, status: string): Promise {
    await new Promise(resolve => setTimeout(resolve, 50));
  }
}

// === NestJS ===
import { Injectable } from '@nestjs/common';
import { EventEmitter2, OnEvent } from '@nestjs/event-emitter';

@Injectable()
export class UserService {
  constructor(private eventEmitter: EventEmitter2) {}

  async createUser(email: string, name: string): Promise {
    const userId = `user_${Date.now()}`;
    await this.saveUserToDatabase(userId, email, name);

    // Emit typed event
    this.eventEmitter.emit('user.created', {
      userId,
      email,
      name,
      createdAt: new Date()
    });

    return userId;
  }

  private async saveUserToDatabase(userId: string, email: string, name: string): Promise {
    // Implementation
  }
}

// Event listeners (observers)
@Injectable()
export class UserCreatedListener {
  @OnEvent('user.created')
  async handleUserCreated(event: UserCreatedEvent) {
    console.log('User created:', event);
    // Send welcome email, update analytics, etc.
  }
}

@Injectable()
export class AuditListener {
  @OnEvent('user.created')
  logUserCreated(event: UserCreatedEvent) {
    console.log(`[AUDIT] User created: ${event.userId}`);
  }

  @OnEvent('order.created')
  logOrderCreated(event: OrderCreatedEvent) {
    console.log(`[AUDIT] Order created: ${event.orderId}`);
  }
}

@Injectable()
export class EmailListener {
  @OnEvent('user.created')
  async sendWelcomeEmail(event: UserCreatedEvent) {
    // Send email
  }

  @OnEvent('order.created')
  async sendOrderConfirmation(event: OrderCreatedEvent) {
    // Send email
  }
}

@Module({
  imports: [EventEmitterModule.forRoot()],
  providers: [UserService, UserCreatedListener, AuditListener, EmailListener],
  exports: [UserService]
})
export class UserModule {}