The Problem
When an application needs to create objects that share a common interface but differ in their concrete implementation, hard-coding the creation logic with new ConcreteClass() scattered throughout the codebase leads to tight coupling. If you later need to add a new product type, you must hunt down every instantiation site and modify it.
In an e-commerce system, a product listing for a physical book, a digital download, and a monthly subscription all share common traits (name, price) but each has unique details (shipping weight vs. download URL vs. billing cycle).
The Solution
The Factory Method pattern defines an abstract createProduct() method in a base creator class. Each subclass overrides this method to produce a specific type of product. The base creator can also contain shared logic (such as adding a timestamp and SKU) that runs regardless of which concrete product is created. Client code works with the abstract creator interface and never needs to know which concrete class is instantiated.
Structure
- Product (
Productinterface) — Defines the common interface that all products must implement. Containsname,price,type, anddetails. - Creator (
ProductCreator) — An abstract class that declares the factory methodcreateProduct()and provides a template methodgetProduct()that calls the factory method and enriches the result with acreatedAttimestamp and a generatedsku. - ConcreteCreator (
PhysicalProductCreator,DigitalProductCreator,SubscriptionProductCreator) — Each subclass implementscreateProduct()to return a specific product type with its own details. - Client (
FactoryMethodController) — Receives a product type string, selects the appropriate creator, and delegates product creation without knowing the concrete class.
Implementation
This implementation models a product catalog system where an e-commerce platform sells three types of products:
- Physical products — items that require shipping, have weight and dimensions, and are assigned to a warehouse.
- Digital products — downloadable files with a URL, file size, format, and license type.
- Subscription products — recurring billing items with a billing cycle, duration, auto-renewal, and trial period.
All three share the same Product interface. The ProductCreator base class handles cross-cutting concerns (SKU generation, creation timestamp) in its getProduct() template method, while each concrete creator fills in the type-specific details.
export interface Product {
name: string;
price: number;
type: string;
details: Record<string, unknown>;
} The ProductCreator.getProduct() method is itself a Template Method — it defines a skeleton algorithm where the createProduct() step is deferred to subclasses. This means adding a new product type (e.g., BundleProductCreator) requires only creating a new subclass without modifying any existing code.
Example Output
When creating a physical product, the factory returns:
{
"name": "Wireless Mouse",
"price": 29.99,
"type": "physical",
"details": {
"weight": "0.5kg",
"dimensions": "30x20x10cm",
"shippingRequired": true,
"warehouse": "WH-001"
},
"createdAt": "2026-02-16T12:00:00.000Z",
"sku": "PHYSICAL-1708099200000"
}
NestJS Integration
In NestJS, the Factory Method pattern maps naturally to the provider system:
- Each concrete creator (
PhysicalProductCreator,DigitalProductCreator,SubscriptionProductCreator) is decorated with@Injectable()and registered as a provider in the module. - The controller injects all creators and selects the correct one based on the incoming request’s
typefield. - NestJS’s built-in
useFactoryprovider syntax is a related but distinct concept: it lets you define a factory function at the module level to create providers with custom logic. The Factory Method pattern goes a step further by using an inheritance hierarchy of creators.
You could also leverage NestJS custom providers with useClass to swap concrete creators via configuration, making the pattern even more flexible in a modular architecture.
When to Use
- You do not know in advance the exact types of objects your code will need to create.
- You want to provide a library or framework where users can extend the creation logic by subclassing.
- You want to centralize and reuse common creation logic (like SKU generation) while allowing the product-specific details to vary.
- You want to decouple the client from the concrete classes it instantiates, making it easy to add new product types without modifying existing code.
- You want to comply with the Open/Closed Principle: open for extension (new creators), closed for modification (existing client code).
When NOT to Use
- When there is only one type of product and no foreseeable need for variation. Introducing the pattern adds unnecessary abstraction.
- When the creation logic is trivial (e.g.,
new SomeClass(arg)) and all products are essentially identical. A simple constructor call is clearer. - When the type hierarchy is unstable and changes affect the creator interface itself. The pattern can amplify churn rather than reduce it.