Enhance modularity in NestJS using the Strategy pattern

Cláudio Rapôso
3 min readJun 22, 2023

Developing robust applications and services requires the use of efficient design patterns. In this article, we will explore the application of the Strategy pattern in conjunction with the NestJS framework. We will see how this pattern can improve the modularity and flexibility of a NestJS application, allowing for easy addition and substitution of business strategies.

What is the Strategy Pattern?

The Strategy Pattern, also known as the Strategy Design Pattern, is one of the design patterns defined by the Gang of Four (GoF). It allows encapsulating different algorithms or behaviors into separate classes, known as strategies, and dynamically selecting which strategy to use at runtime. This facilitates the interchange of algorithms without needing to modify the code that uses them, promoting greater flexibility and extensibility in software design.

In other words, it is a behavioral design pattern that enables the definition of a family of algorithms encapsulated in separate classes. Each class represents a specific strategy that can be dynamically selected and used at runtime. This approach makes it easier to add new strategies and modify existing ones without the need to modify the existing code.

Benefits of the Strategy Pattern:

Modularity:

By separating strategies into independent classes, the Strategy Pattern promotes code modularity. Each strategy is responsible for a single task and can be developed and tested separately.

Flexibility:

With the Strategy Pattern, it’s possible to switch strategies at runtime without impacting other parts of the system. This allows adapting the application’s behavior according to specific needs at any given time.

Code reusability:

Strategy classes can be reused in different contexts, facilitating development and avoiding code duplication.

Testability:

Since strategies are encapsulated in independent classes, it is easier to write unit tests for each strategy, ensuring code quality.

Applying the Strategy Pattern in NestJS:

When developing an API with NestJS, we often encounter the need to implement different strategies to perform a specific task, such as processing a payment request. By using the Strategy Pattern, we can define a common interface for these strategies and create separate classes for each specific implementation. Let’s see a practical example:

Step 1: Defining the Strategy Interface

Firstly, we need to create an interface that represents the contract for all strategies. For example, we can create an interface called “PaymentStrategy” with a method called “processPayment”.

// payment.strategy.ts
export interface PaymentStrategy {
execute(amount: number): Promise<void>;
}

Step 2: Implementing the Strategies

Now, we can create different classes that implement the “PaymentStrategy” interface according to our system’s needs. Each concrete class will be responsible for implementing the specific logic to process the payment.

// credit-card.strategy.ts
export class CreditCardStrategy implements PaymentStrategy {
async execute(amount: number): Promise<void> {
// to credit card
}
}

// paypal.strategy.ts
export class PayPalStrategy implements PaymentStrategy {
async execute(amount: number): Promise<void> {
// to paypal
}
}

Step 3: Using Strategies in NestJS

Now that we have our strategies implemented, we can use them within the context of NestJS. We can inject the “PaymentStrategy” interface into a service and select the appropriate strategy based on certain conditions or user preferences.

// payment.service.ts
@Injectable()
export class PaymentService {
constructor(
@Inject('PaymentStrategy')
private readonly paymentStrategy: PaymentStrategy,
) {}

async processPayment(amount: number): Promise<void> {
// Business logic before the payment process
await this.paymentStrategy.execute(amount);
// Business logic after the payment process
}
}

Step 4: Configuring Dependency Injection in the Module

To ensure that NestJS knows which strategy to use, we need to configure the dependency injection in the corresponding module. We can use the custom providers feature of NestJS to bind a specific strategy to the “PaymentStrategy” interface.

// payment.module.ts
@Module({
providers: [
{
provide: 'PaymentStrategy',
useClass: CreditCardStrategy, // or PayPalStrategy
},
PaymentService,
],
})
export class PaymentModule {}

Conclusion

The Strategy Pattern is a powerful tool to improve the modularity and flexibility of NestJS applications. By separating algorithms into independent classes, we can easily add, replace, and combine strategies. This results in more organized, reusable, and maintainable code. By using the Strategy Pattern in conjunction with NestJS, we can build more robust and scalable applications tailored to business needs.

To read the article in Portuguese:

--

--

Cláudio Rapôso

Microsoft MVP | Software Architect | Teacher | React | React Native | Node | Autor do Livro NestJS: Do Zero até a primeira API | MCT | 12x Msft | 2x AWS