Design Patterns in JavaScript: Advanced Techniques with Examples

Online Python Trainer for Beginners

Learn Python easily without overwhelming theory. Solve practical tasks with automatic checking, get hints in Russian, and write code directly in your browser — no installation required.

Start Course

Advanced Design Patterns in JavaScript: From Theory to Practice

\n\n

Design patterns are proven solutions to common problems in software development. For a JavaScript developer working with Node.js, understanding advanced patterns opens the path to creating scalable, flexible, and easily maintainable systems. In this article, we will examine four key patterns: Observer, Proxy, Chain of Responsibility, and Strategy. Each will be illustrated with a practical example in Node.js.

\n\n

1. Observer Pattern: Event-Driven Architecture in Node.js

\n\n

Observer is one of the fundamental behavioral patterns. It defines a one-to-many dependency between objects: when one object (the subject) changes its state, all dependent objects (observers) are automatically notified and updated. In Node.js, this pattern is the foundation of the EventEmitter module.

\n\n

Example: notification system for an online store

\n

Imagine we have a server that manages orders. When an order status changes, several subsystems need to be notified: logging, email sending, and analytics updates.

\n\n
const EventEmitter = require('events');\n\n// Subject\nclass OrderManager extends EventEmitter {\n    constructor() {\n        super();\n        this.orders = [];\n    }\n\n    createOrder(orderId, items) {\n        const order = { orderId, items, status: 'created', createdAt: new Date() };\n        this.orders.push(order);\n        this.emit('order:created', order);\n        return order;\n    }\n\n    updateStatus(orderId, newStatus) {\n        const order = this.orders.find(o => o.orderId === orderId);\n        if (order) {\n            order.status = newStatus;\n            this.emit('order:statusChanged', order);\n        }\n    }\n}\n\n// Observers\nfunction logObserver(order) {\n    console.log(`[LOG] Order ${order.orderId}: status changed to ${order.status} at ${new Date().toISOString()}`);\n}\n\nfunction emailObserver(order) {\n    console.log(`[EMAIL] Sending notification for order ${order.orderId} with status ${order.status}`);\n}\n\nfunction analyticsObserver(order) {\n    console.log(`[ANALYTICS] Recording event: order ${order.orderId} -> ${order.status}`);\n}\n\n// Usage\nconst manager = new OrderManager();\nmanager.on('order:created', logObserver);\nmanager.on('order:statusChanged', logObserver);\nmanager.on('order:statusChanged', emailObserver);\nmanager.on('order:statusChanged', analyticsObserver);\n\nmanager.createOrder('ORD-001', ['Book', 'Pen']);\nmanager.updateStatus('ORD-001', 'shipped');\n
\n\n

This approach allows adding or removing observers without changing the subject's code, making the system flexible and easily extensible.

\n\n

2. Proxy Pattern: Access Control and Caching

\n\n

Proxy provides a surrogate object that controls access to another object. In JavaScript, this pattern is implemented through the built-in Proxy object, which allows intercepting and redefining fundamental operations (property access, function calls, etc.).

\n\n

Example: caching proxy for API requests

\n

Suppose we have a service that makes expensive requests to an external API. We want to cache the results to avoid repeated calls.

\n\n
// Real Subject\nclass ExternalApiService {\n    async fetchUser(id) {\n        console.log(`Fetching user ${id} from external API...`);\n        // Simulating network delay\n        await new Promise(resolve => setTimeout(resolve, 1000));\n        return { id, name: `User ${id}`, email: `user${id}@example.com` };\n    }\n}\n\n// Proxy\nclass CachingProxy {\n    constructor(realService) {\n        this.realService = realService;\n        this.cache = new M                

Blogs

Book Recommendations