Design Patterns in Node.js: A Practical Reference with Real Examples
A detailed Node.js guide to GoF design patterns with plain-English explanations, when to use them, tradeoffs to watch for, and practical examples you will actually recognize in backend code.
Published on 11 apr 2026

Table of Contents
- 1. Why Design Patterns Matter
- 2. The Three GoF Categories
- 3. Creational Patterns
- 3.1 Singleton
- 3.2 Factory Method
- 3.3 Builder
- 3.4 Prototype
- 3.5 Abstract Factory
- 4. Structural Patterns
- 4.1 Adapter
- 4.2 Decorator
- 4.3 Proxy
- 4.4 Facade
- 4.5 Composite
- 4.6 Bridge
- 4.7 Flyweight
- 5. Behavioral Patterns
- 5.1 Observer
- 5.2 Strategy
- 5.3 Command
- 5.4 Chain of Responsibility
- 5.5 Iterator
- 5.6 Template Method
- 5.7 State
- 5.8 Mediator
- 5.9 Memento
- 5.10 Visitor
- 5.11 Interpreter
- 6. How These Patterns Show Up in Real Node.js Systems
- 7. How to Choose the Right Pattern
- 8. Common Mistakes
- 9. Quick Reference
- 10. Final Takeaway
1. Why Design Patterns Matter
Design patterns are reusable solutions to common software design problems. They are not libraries and they are not rules. They are proven approaches that help us name a problem clearly and choose a structure that keeps code maintainable as the project grows.
In Node.js, patterns matter even more because applications often grow from a few scripts into larger systems with:
- APIs
- background jobs
- event-driven flows
- integrations with external SDKs
- caching layers
- queues, streams, and database access
Without patterns, the code still works for a while, but it becomes harder to extend safely.
2. The Three GoF Categories
The Gang of Four groups the 23 classic patterns into three families:
2.1 Creational Patterns
These focus on object creation.
They answer:
- Who creates the object?
- How much control do we need over construction?
- Should creation logic be centralized or hidden?
2.2 Structural Patterns
These focus on composition.
They answer:
- How do multiple objects fit together?
- How do we hide complexity?
- How do we make different interfaces cooperate?
2.3 Behavioral Patterns
These focus on communication and responsibility.
They answer:
- How do objects collaborate?
- How do we switch behavior cleanly?
- How do we avoid giant if/else blocks and tight coupling?
3. Creational Patterns
3.1 Singleton
What it is
A Singleton ensures that only one instance of a class or service exists in the application and gives us a single access point to it.
Why it exists
Some resources should not be recreated repeatedly:
- database clients
- logger instances
- configuration loaders
- shared caches
Node.js angle
In Node.js, module caching already gives us a lightweight singleton-like behavior. If you export one object from a module, every importer gets the same instance.
When to use it
- Shared DB or Redis client
- Configuration service loaded once at startup
- Central logger
Watch out
Singletons can quietly turn into global mutable state. That makes tests harder and can hide dependencies between modules.
Good mental model
Use Singleton for shared infrastructure, not for ordinary business objects.
class Database { static #instance = null; constructor() { if (Database.#instance) return Database.#instance; this.connection = 'Connected'; Database.#instance = this; } static getInstance() { if (!Database.#instance) new Database(); return Database.#instance; } }
3.2 Factory Method
What it is
Factory Method moves object creation into a dedicated method so callers do not need to know the exact class being instantiated.
Why it exists
If your app creates different implementations based on runtime input, the creation logic can quickly spread everywhere.
Node.js angle
Factories are common in:
- payment gateway setup
- notification providers
- storage adapters
- database driver selection
When to use it
- You create one of several related classes
- The caller should not know concrete implementation details
- You want to keep branching logic in one place
Tradeoff
If there are only two tiny cases and they are unlikely to grow, a factory may be unnecessary abstraction.
class NotifierFactory { static create(type) { const map = { email: EmailNotifier, sms: SMSNotifier, push: PushNotifier, }; if (!map[type]) throw new Error(`Unknown notifier: ${type}`); return new map[type](); } }
3.3 Builder
What it is
Builder constructs complex objects step by step instead of forcing everything through one large constructor.
Why it exists
Some objects need optional parts, ordered steps, or fluent composition. A builder keeps that readable.
Node.js angle
This pattern shows up constantly in:
- query builders
- request builders
- email builders
- pipeline configuration APIs
Why it feels natural in JavaScript
Chaining methods is ergonomic in JS, so Builder often reads very cleanly.
When to use it
- Too many constructor parameters
- Many optional fields
- Stepwise assembly improves readability
Tradeoff
If the object is simple, a plain object literal is often better.
const sql = new QueryBuilder() .select('id, name') .from('users') .where('age > 18') .limit(10) .build();
3.4 Prototype
What it is
Prototype creates new objects by cloning an existing one instead of building from scratch every time.
Why it exists
If initializing the original object is expensive or the base configuration is already correct, cloning is faster and simpler.
Node.js angle
Common cases include:
- environment config cloning
- test fixtures
- seeded job payloads
- copying template objects before customization
When to use it
- Base object creation is expensive
- Variants differ only slightly
- You want to duplicate a safe default and then tweak it
Watch out
Shallow copies can cause surprising bugs when nested data is shared. Prefer deep cloning when needed.
3.5 Abstract Factory
What it is
Abstract Factory creates families of related objects that are meant to work together.
Why it exists
Sometimes one choice affects multiple objects at once. If the app chooses mysql, then connection objects, query builders, and transaction helpers should all come from the same family.
Node.js angle
Useful for:
- multi-database systems
- cloud-provider abstraction
- environment-specific service families
When to use it
- A single runtime choice determines multiple related objects
- You want consistency across a product family
- You want to swap implementations without touching business logic
Tradeoff
This pattern is powerful, but it adds structure quickly. It is best when there is a real family relationship, not just one interchangeable class.
4. Structural Patterns
4.1 Adapter
What it is
Adapter converts one interface into another so incompatible code can work together.
Why it exists
Third-party SDKs rarely match the interface you want in your app. An adapter lets the rest of your code stay clean.
Node.js angle
Adapters are extremely practical for:
- payment SDK wrappers
- email/SMS providers
- cloud service SDKs
- migrating from one vendor to another
Why it is valuable
The adapter isolates vendor-specific naming, payload formats, and quirks. That means the rest of your code depends on your interface, not theirs.
When to use it
- You do not control the source API
- You want to standardize multiple vendors behind one contract
- You are in a migration and need compatibility
4.2 Decorator
What it is
Decorator adds behavior to an object dynamically by wrapping it rather than modifying the original class.
Why it exists
If you keep making subclasses like LoggedCachedAuthorizedUserService, the class tree becomes unmanageable. Decorator lets us stack behaviors instead.
Node.js angle
Decorator-like composition is common in:
- Express middleware chains
- caching wrappers
- logging wrappers
- retry wrappers
When to use it
- You need optional layered behavior
- You want to compose features at runtime
- Subclass explosion is becoming a problem
Watch out
Too many wrappers can make debugging harder, especially if each layer mutates inputs or handles errors differently.
4.3 Proxy
What it is
Proxy is a stand-in object that controls access to the real object.
Why it exists
Sometimes we do not want direct access. We may need:
- lazy loading
- authorization checks
- caching
- rate limiting
- logging
Proxy vs Decorator
They can look similar, but the intent is different:
- Decorator adds behavior
- Proxy controls access
Node.js angle
This shows up in ORM lazy-loading, cache fronting, and API clients that defer expensive work until the last possible moment.
4.4 Facade
What it is
Facade provides a simple interface over a more complex subsystem.
Why it exists
Clients should not have to know every internal step required to complete a business operation.
Node.js angle
A facade is a great fit for service orchestration:
- placing an order
- onboarding a user
- processing a refund
- publishing content
Benefit
It lowers cognitive load. The caller sees one clear entry point while the facade coordinates multiple services internally.
Watch out
A facade should simplify orchestration, not become a giant god object with all business logic dumped into it.
4.5 Composite
What it is
Composite lets us treat individual items and groups of items the same way.
Why it exists
Tree structures appear everywhere. Files and folders are the classic example, but so are menu trees, nested comments, and component trees.
Node.js angle
Common uses:
- nested routers
- AST nodes
- menu builders
- hierarchical permissions
When to use it
Use Composite when leaves and containers should support the same operations, such as render(), getSize(), or execute().
4.6 Bridge
What it is
Bridge separates an abstraction from its implementation so the two can vary independently.
Why it exists
If you combine every abstraction with every implementation directly, the number of classes grows too fast.
Example idea
Think of a notification system:
- abstraction: alert type
- implementation: delivery channel
Instead of creating UrgentEmailAlert, UrgentSMSAlert, ReminderEmailAlert, and so on, Bridge lets us mix abstraction and implementation more cleanly.
Node.js angle
Useful in multi-provider integrations and rendering systems.
4.7 Flyweight
What it is
Flyweight reduces memory usage by sharing common intrinsic state across many objects.
Why it exists
If thousands of objects share the same repeated data, we should store that shared part once.
Node.js angle
This can matter in:
- caching repeated metadata
- connection pools
- document rendering systems
- in-memory object-heavy services
Watch out
Flyweight adds complexity and is only worth it when object counts or memory costs are genuinely high.
5. Behavioral Patterns
5.1 Observer
What it is
Observer defines a one-to-many relationship where one subject notifies multiple listeners when something changes.
Why it exists
The publisher should not need to know who is listening. It just emits an event.
Node.js angle
This is core to Node.js culture. EventEmitter is essentially Observer in practice.
Real uses
- order events
- audit logging
- email triggers
- analytics tracking
- websocket broadcasts
Watch out
Uncontrolled event systems can become hard to trace. If too much business logic hides in listeners, debugging gets messy.
5.2 Strategy
What it is
Strategy packages interchangeable algorithms behind a common interface so behavior can be swapped at runtime.
Why it exists
It replaces branching logic like:
if (provider === 'stripe') { ... } else if (provider === 'paypal') { ... } else if (provider === 'crypto') { ... }
with pluggable behavior.
Node.js angle
One of the most practical backend patterns for:
- authentication providers
- payment processors
- sorting/filtering rules
- serializer choices
When to use it
- Several variants share the same goal
- Algorithms change independently from the calling code
- New variants are likely
5.3 Command
What it is
Command turns an action into an object.
Why it exists
When an action becomes an object, we can:
- queue it
- log it
- retry it
- undo it
- schedule it
Node.js angle
This maps naturally to:
- job queues
- task schedulers
- migrations
- undo/redo systems
Strong benefit
It decouples the invoker from the actual work being performed.
5.4 Chain of Responsibility
What it is
Chain of Responsibility passes a request through a sequence of handlers. Each handler can process it, reject it, or pass it forward.
Why it exists
It helps us avoid monolithic request handlers full of unrelated responsibilities.
Node.js angle
Express middleware is the clearest example:
- auth
- logging
- validation
- rate limiting
- final handler
Watch out
If the chain order is unclear, bugs become subtle. Middleware order is behavior.
5.5 Iterator
What it is
Iterator provides a standard way to traverse a collection without exposing its internal structure.
Why it exists
It separates traversal logic from the collection itself.
Node.js angle
JavaScript already uses this pattern heavily:
for...of- generators
- streams
- async iterables
Why it is important
This pattern makes lazy processing possible, which is especially helpful for large datasets or paginated APIs.
5.6 Template Method
What it is
Template Method defines the overall structure of an algorithm in a base class while letting subclasses fill in specific steps.
Why it exists
Many workflows have a stable sequence but customizable steps.
Node.js angle
Good fits include:
- data export flows
- ETL pipelines
- report generation
- content transformation pipelines
Benefit
We keep order and shared behavior consistent while allowing local specialization.
5.7 State
What it is
State changes an object’s behavior when its internal state changes.
Why it exists
Instead of giant conditionals based on status fields, each state object handles behavior appropriate to that state.
Node.js angle
Common uses:
- order lifecycle
- payment lifecycle
- workflow engines
- media processing stages
Benefit
State makes transitions explicit and keeps behavior near the state that owns it.
5.8 Mediator
What it is
Mediator centralizes communication between objects so they do not talk to each other directly.
Why it exists
Direct many-to-many communication becomes tangled quickly. A mediator reduces coupling by routing collaboration through one coordinator.
Node.js angle
Useful in:
- chat systems
- workflow coordinators
- event buses
- UI coordination layers
5.9 Memento
What it is
Memento captures and restores an object’s internal state without exposing its internals broadly.
Why it exists
It supports rollback, history, and checkpoints.
Node.js angle
This fits:
- editor history
- draft recovery
- session snapshots
- workflow restore points
5.10 Visitor
What it is
Visitor lets us add new operations to a structure without modifying the objects inside that structure.
Why it exists
If you have a stable object model but keep adding new operations, Visitor avoids touching every class repeatedly.
Node.js angle
Very common in:
- AST processing
- compilers
- linters
- code transforms
5.11 Interpreter
What it is
Interpreter defines a way to evaluate a grammar or expression language.
Why it exists
When the problem is about understanding and executing a small language, Interpreter gives structure to parsing and evaluation.
Node.js angle
Useful for:
- filter expressions
- rule engines
- DSLs
- query-like mini languages
6. How These Patterns Show Up in Real Node.js Systems
Patterns rarely appear alone. In real projects they combine naturally.
A typical backend feature might look like this:
Factorychooses the correct payment providerStrategyexecutes the provider-specific payment algorithmAdapterwraps the third-party SDKFacadeexposes onecheckout()callObserveremitsorderPlacedCommandpushes a follow-up job into a queueSingletonprovides the shared logger and DB client
That is one reason patterns are valuable: they give us a language to describe architecture decisions precisely.
7. How to Choose the Right Pattern
When choosing a pattern, ask:
- Is the problem about creation, composition, or behavior?
- Am I reducing coupling or just adding abstraction?
- Will this make future changes easier?
- Is the pattern solving a real repeated problem?
If the answer to the last question is no, simpler code is usually the better design.
8. Common Mistakes
8.1 Using patterns too early
Patterns are helpful when they solve repeated design pressure. Adding them before the need appears often creates unnecessary indirection.
8.2 Confusing patterns with frameworks
A framework may use many patterns internally, but the pattern itself is still just a design idea.
8.3 Over-abstracting simple code
Not every if statement needs Strategy. Not every helper needs a Facade. Not every shared object needs Singleton.
8.4 Ignoring tradeoffs
Every pattern improves one dimension while adding some complexity somewhere else.
9. Quick Reference
| Pattern | Main Goal | Strong Node.js Fit |
|---|---|---|
| Singleton | One shared instance | DB client, logger, config |
| Factory Method | Centralize creation | providers, drivers, gateways |
| Builder | Step-by-step construction | query builders, request builders |
| Prototype | Clone base objects | configs, fixtures, templates |
| Abstract Factory | Create related object families | multi-DB, multi-cloud |
| Adapter | Convert interfaces | external SDK wrappers |
| Decorator | Add behavior dynamically | logging, caching, retries |
| Proxy | Control access | lazy load, auth, caching |
| Facade | Hide subsystem complexity | service orchestration |
| Composite | Treat leaves and groups uniformly | trees, nested routes |
| Bridge | Separate abstraction from implementation | provider-independent APIs |
| Flyweight | Share common state | memory-sensitive systems |
| Observer | Event notification | EventEmitter, pub/sub |
| Strategy | Swap algorithms | auth, payments, serialization |
| Command | Encapsulate actions | jobs, undo, retries |
| Chain of Responsibility | Sequential handlers | middleware pipelines |
| Iterator | Traverse collections | generators, streams |
| Template Method | Fixed workflow, customizable steps | exports, ETL |
| State | Behavior changes by state | workflows, order status |
| Mediator | Centralize communication | event bus, coordinators |
| Memento | Save and restore state | history, snapshots |
| Visitor | Add operations to structures | AST tools, linters |
| Interpreter | Evaluate mini-language | rule engines, parsers |
10. Final Takeaway
Design patterns are best treated as vocabulary, not ceremony.
They help us:
- describe code structure more clearly
- reduce coupling
- make change safer
- recognize proven solutions faster
In Node.js, the most practical patterns show up every day: Singleton, Factory, Adapter, Observer, Strategy, Facade, and Chain of Responsibility.
If you understand those deeply, you already have a strong foundation for building better backend systems.


