Toll Free:

1800 889 7020

Top Node.js Design Patterns & Microservice with Examples

In this blog, you will learn about the popular design patterns in Node.JS Development and how to implement Node.js Microservices frameworks with real-time examples.

Node.js Design Patterns basics:

Node.js is the most widely used platform where you can build scalable and high-performance applications. As Node.js applications grow in complexity, so you need to leverage well-established design patterns and it is essential to maintain code quality | readability, and scalability.

In this blog, I have explained design patterns in Node.js and their real-time implementations.

Why Design Patterns Matter in Node.js?

Design patterns offer better solutions to common software design problems. They help in

  • Code reusability Enhancement
  • Maintainability Improvement.
  • Complex applications structure efficiently.
  • Reducing redundant code and increasing performance.

Node.js follows as I already mentioned an event-driven | non-blocking I/O model & it makes certain patterns particularly effective.

Singleton Pattern

When you need a single shared instance of a resource (For example: database connection, configuration settings).

Implementation:

Singleton Pattern
const db1 = new Database();
const db2 = new Database();
console.log(db1 === db2); // true (Same instance)

Prevents unnecessary creation of multiple instances, optimizing resource usage.

Factory Pattern

When we need a centralized mechanism to create objects dynamically based on conditions.

Implementation:

Factory Pattern

Simplifies object creation logic while maintaining flexibility.

Observer Pattern:

When multiple parts of the application need to react to an event (Ex: real-time notifications, event-driven architectures).

Implementation:

Observer Pattern:

Enables event-driven architecture and it makes the system more scalable and modular.

Repository Pattern

When abstracting database logic to provide a clean separation between data access and business logic.

Implementation:

Repository Pattern

Keeps business logic separate from database operations, improving maintainability.

Decorator Pattern

When dynamically adding functionalities to an object without modifying its existing structure.

Implementation:

Decorator Pattern
const LoggedAuthService = Logger(AuthService);
console.log(LoggedAuthService.execute(true));

It enhances functionality dynamically without altering the original object.

Proxy Pattern

When controlling access to an object, such as caching responses or restricting API calls.

Implementation:

Proxy Pattern

Reduces unnecessary API calls and improves performance.

  • Design patterns play a vital role in building maintainable scalable and efficient Node.js applications.
  • Actually, by applying the right pattern for the right problem, we can enhance their application’s structure, performance, and flexibility.

KPIs:

  • Singleton ensures a single instance of a resource.
  • Factory simplifies object creation.
  • Observer enables event-driven architectures.
  • Repository abstracts database logic.
  • Decorator extends functionality dynamically.
  • Proxy optimizes API calls with caching.

You need to understand and implement these patterns will help you build robust and scalable Node.js applications. Stay ahead in software development by mastering these essential design patterns!

You can try to implement these patterns in your projects and see the difference.

Node.js Design Patterns Architectural Approaches:

Factory Pattern

When you need a centralized mechanism to create objects dynamically based on conditions.

Real-Time Example: Payment Gateway Service

A microservice that handles different payment providers (for example: PayPal, Stripe, etc.) can use the Factory pattern to create appropriate payment service instances based on the request.

Implementation example:

Factory Pattern_2
const paymentService = PaymentFactory.createPaymentService('paypal');
paymentService.processPayment(100);

Simplifies object creation and ensures flexibility in choosing payment providers.

Observer Pattern

When multiple parts of the application need to react to an event (for example: real-time notifications, event-driven architectures).

Real-Time Example: Order Processing Microservice

An e-commerce system where multiple services (e.g., Notification, Inventory, Shipping) need to listen to order events.

Implementation:

Observer Pattern_1
Observer Pattern_2

It enables real-time event handling and decouples different services thereby making them independent as well as scalable.

Proxy Pattern

When you want to control access to an object, such as caching responses or restricting API calls.

Real-Time Example: API Caching for Product Microservice

A product service can use a proxy to cache product details and reduce redundant API calls to the database.

Implementation:

Proxy Pattern_2
  • Reduces redundant API calls and enhances performance as well as optimizes response times.
  • Factory Pattern ensures flexible object creation in microservices like payment handling.
  • Observer Pattern enables real-time event-driven handling in systems like order processing.
  • Proxy Pattern optimizes performance through caching, reducing redundant API calls.

Understanding and implementing these patterns will help you build robust and scalable Node.js microservices.

A real-world microservices: Implementation using Express.js and NestJS, leveraging design patterns for structured development.

Scenario: E-commerce Order Processing Microservice

We will implement an Order Microservice in both Express.js and NestJS using the Factory Pattern (for payment processing) and Observer Pattern (for event-based communication).

Factory Pattern: Helps dynamically create payment processing objects (PayPal, Stripe, etc.).

Observer Pattern: Enables real-time notifications when an order is placed.

Express.js Implementation (Using Factory + Observer Pattern)

Order Microservice (Express.js)

Order Microservice

Payment Factory (Express.js)

Payment Factory
  • Run Express Microservice:
  • node index.js

Test API using Postman:

POST http://localhost:3000/order
{
  "orderId": "ORD123",
  "amount": 100,
  "paymentType": "paypal"
}

NestJS Implementation (Using Factory + Observer Pattern)

Order Microservice (NestJS)

Order Microservice NestJS

Event Listener (Observer) in NestJS

Event Listener in NestJS

Payment Factory (NestJS)

Payment Factory in NestJS
  • Run NestJS Microservice:
  • npm run start

Test API using Postman:

POST http://localhost:3000/order
{
  "orderId": "ORD123",
  "amount": 100,
  "paymentType": "stripe"
}

Express.js vs. NestJS for Microservices Development

FeatureExpress.jsNestJS
ArchitectureUnstructuredModular & Layered
PerformanceHigh but unstructuredOptimized for large apps
TypeScript SupportOptionalBuilt-in
Dependency InjectionManualBuilt-in
Microservices SupportRequires manual setupOut-of-the-box
Built-in Event SystemNeeds EventEmitterNestJS EventEmitter
  • Express.js is lightweight and faster to set up but requires manual event handling.
  • NestJS provides a scalable, structured, and enterprise-ready microservices framework.
  • Design Patterns like Factory & Observer help in modularizing and decoupling the system.

For complex, the best design patterns depend on your use case. Here are the top design patterns that help structure Express.js microservices efficiently:

Factory Pattern (For Dynamic Object Creation)

  • When you need different implementations of a service (For ex: payment gateways like PayPal or Stripe).
  • It reduces tight coupling and promotes extensibility.
  • Example: Payment Service with Factory Pattern

Factory Pattern For Dynamic
module.exports = PaymentFactory;

Use in Microservice (Express.js)

Use in Microservice

Observer Pattern (For Event-Based Communication)

  • When microservices need to notify other services asynchronously (e.g., Order Service triggers Notification Service).
  • Decouples services improve scalability and support event-driven architecture.
  • Example: Order Microservice with Observer Pattern
Observer Pattern for Event
Extending to Notification Microservice
eventEmitter.on('orderPlaced', (order) => {
  console.log(`📩 Sending Email: Order ${order.id} has been placed.`);
});

Proxy Pattern (For Caching & Performance Optimization)

  • When a microservice calls an external API frequently and needs to cache results to reduce latency and improve performance.
  • It reduces API load, speeds up responses, and minimizes redundant requests.
  • Example: API Proxy with Caching (Using Redis)
Proxy Pattern For Caching

Strategy Pattern (For Handling Multiple Business Logic Strategies)

  • When different algorithms or strategies need to be dynamically selected at runtime (For example: different discount calculations for VIP and regular users).
  • It Makes the system extensible and improves maintainability.
  • Example: Discount Strategy for E-commerce Microservice
Strategy Pattern for handling
module.exports = DiscountFactory;

Use in Microservice

Use in Microservice1

For complex microservices in Express.js, you should:

  • Use Factory Pattern to create different services dynamically.
  • Implement Observer Pattern for event-driven microservices.
  • Optimize performance using Proxy Pattern with caching.
  • Use Strategy Patterns for different business rules dynamically.

Express.js microservices architecture diagram or Kafka-based event-driven model:

Express.js microservices architecture diagram

The Saga Pattern is a design pattern that helps manage long-running transactions and also it ensures consistency across distributed microservices. The internal architecture of the Saga Pattern involves breaking down a business process into a series of small isolated and independently executable steps i.e. actions that can either be completed or compensated for if a failure occurs.

The internal architecture of the Saga Pattern typically includes several key components:

Key Components of Saga Pattern Architecture

Microservices

Each microservice is responsible for a particular task or step in the saga. A microservice can be thought of as a participant in the saga.

Saga Orchestrator (or Coordinator):

  • This is the central service that controls the flow of the saga.
  • It manages the sequence of actions and coordinates the execution of each step.
  • It is responsible for handling the success and failure paths, as well as triggering compensation actions if necessary.

Actions (or Steps):

  • Each step in the saga is an isolated action (such as creating an order, processing payment, or updating inventory).
  • Each action is typically implemented as a separate microservice.
  • If one action fails, a compensating action is triggered to ensure consistency across the system.

Compensation Actions:

  • These are actions that undo the effects of a previously completed action if something goes wrong later in the saga.
  • For example, if a payment action is successful, but inventory fails, the compensation might involve a refund

Events:

  • Events are used to communicate the outcomes of each action to the saga orchestrator and other microservices.
  • They are typically published to notify other services about the success or failure of each step (e.g., order.created, payment.failed).

State Management:

  • A saga needs to track the state of each action and the entire saga.
  • This state management ensures that the orchestrator can know whether to proceed with the next step or trigger compensation if a failure occurs.

Saga Pattern Flow Overview:

Here’s an outline of how the Saga Pattern typically works:

Initiate Saga:

  • The Saga is triggered, often by an external client or another service. The orchestrator starts the saga and coordinates the execution of its actions.

Execute Steps:

  • Each step (action) is executed sequentially or in parallel depending on the requirements. For example, in an order process, the saga might first create an order, then process payment, and then update inventory.

Event-Based Communication:

  • After each action what happens is the respective microservice emits an event to indicate the result (for example: “order. created”, “payment.success”, “inventory.updated”).
  • The central orchestrator listens to these events and then makes decisions regarding what action needs to be taken next.

Failure Handling:

If a step fails, the orchestrator can either:

  • Trigger compensating actions to undo any successful actions.
  • Retry the failed step based on a predefined policy.
  • Abort the entire saga, rolling back all previously executed actions.

End of Saga:

  • The saga ends when all actions are completed or all compensation actions are executed. The orchestrator ensures that the system is in a consistent state.

What are Saga Pattern Types: Orchestration

There are two primary ways of coordinating a saga:

Orchestration-based Saga:

  • A central Orchestrator controls the flow of the saga and calls each service (microservice) in a predefined sequence.
  • The orchestrator ensures that compensating actions are triggered when a failure occurs.
  • The orchestrator is responsible for managing the saga state.

Choreography-based Saga:

  • In this model, each service involved in the saga knows about the next step in the process and triggers the next service when it finishes its task.
  • There is no central orchestrator. Services publish events and listen to events to decide their actions.
  • Each service is responsible for managing its state and compensating actions.

Example of Saga Pattern Workflow

  1. Order Service creates an order and publishes an event (order.created).
  2. Payment Service listens for the event and processes payment, publishing an event (payment.success or payment.failed).
  3. Inventory Service listens for the payment success event and updates inventory, publishing an event (inventory.updated).
  4. If any service fails (e.g., payment fails), the orchestrator will trigger compensation actions in reverse order (e.g., refund the payment and cancel the order).

Internal Architecture Diagram:

Internal Architecture Diagram:

Saga Architecture Benefits:

  • Services are loosely coupled, with each service performing a specific task without being tightly dependent on others.
  • Resilience: The pattern allows services to fail gracefully, with compensations in place to maintain consistency.
  • Scalability: Each action in the saga is independent, meaning it can be scaled independently.
  • Flexibility: Based on the nature of the failure where failure reasons are different, then in that case different compensation strategies can be implemented.

Saga design pattern implementation in Node.js Moleculer framework:

In Moleculer (a Node.js microservices framework), we can implement the Saga Pattern where we can use Moleculer’s Action Handlers and Event Handlers to manage the workflows that are long-running ones.

Given below is a basic example to implement the Saga Pattern in Moleculer with microservices.

1. Set up your basic project

  • mkdir moleculer-saga-example
  • cd moleculer-saga-example
  • npm init -y
  • npm install molecular

2. Create your services

  • order-service.js- It will handle the order process.
  • payment-service.js – It will handle the payment transaction.
  • inventory-service.js` – It will manage inventory.

3. Define services

 `order-service.js`

order-service.js

`payment-service.js`

payment-service.js

`inventory-service.js`

inventory-service.js

4. Implementing the Saga Pattern using Events

Now, let’s implement the Saga Pattern using the Events and Rollback mechanism.

 `saga-service.js`

saga-service.js

5. Rollback Actions

For rollback purposes, define compensating actions in `payment-service.js` and `inventory-service.js`.

`payment-service.js` (Add rollback action)

rollback action

`inventory-service.js` (Add rollback action)

inventory rollback action

6. Running the Services

Start the services in different terminals or processes:

  • order-service.js
  • payment-service.js
  • inventory-service.js
  • saga-service.js

7. Start Saga Workflow

In saga-service.js, call the createOrderSaga action:

Start Saga Workflow

This will trigger the saga, and in case of any failure in the process (For example: payment fails), it will roll back the actions (such as refunding payment and rolling back inventory).

Conclusion & Key Concepts:

  1. Saga Coordination: The Saga Pattern splits the long-running transaction into smaller isolated services.
  2. Event Handling: The services listen for specific events (For example: “payment.failed”) to trigger compensation actions if one of the steps fails.
  3. Compensation: In case of a failure, the saga ensures that the previous actions are undone (rollback) to maintain consistency across microservices.

felipe-hicks

Felipe Hicks

Experienced IT professional with a strong background in software development and a demonstrated history of working in the outsourcing and offshoring industry. Skilled in developing scalable applications, implementing modern architectural patterns, and leveraging cloud technologies. Proficient in cross-platform development and passionate about driving innovation and delivering tailored solutions. Committed to staying updated with emerging technologies and collaborating effectively to achieve project goals and exceed client expectations

Scroll to Top