The type-safe builder pattern helps you create objects step-by-step while making sure you don't miss any required parts. It uses TypeScript's types to catch mistakes before running the code.
0
0
Type-safe builder pattern in Typescript
Introduction
When you want to build complex objects with many options.
When you want to avoid errors by forgetting to set required values.
When you want clear and readable code for creating objects.
When you want to guide users of your code to set properties in the right order.
When you want to prevent invalid object states at compile time.
Syntax
Typescript
class Builder<Step = Initial> { private data: Partial<MyObject> = {}; setPartA(value: string): Builder<NextStep> { this.data.partA = value; return this as unknown as Builder<NextStep>; } setPartB(value: number): Builder<FinalStep> { this.data.partB = value; return this as unknown as Builder<FinalStep>; } build(this: Builder<FinalStep>): MyObject { return this.data as MyObject; } }
Use generic types to track which parts are set.
The build method only works when all required parts are set.
Examples
This example shows a builder for a
Car object. You must set make, model, and year before calling build.Typescript
interface Car {
make: string;
model: string;
year: number;
}
class CarBuilder<MakeSet = false, ModelSet = false, YearSet = false> {
private car: Partial<Car> = {};
setMake(make: string): CarBuilder<true, ModelSet, YearSet> {
this.car.make = make;
return this as any;
}
setModel(model: string): CarBuilder<MakeSet, true, YearSet> {
this.car.model = model;
return this as any;
}
setYear(year: number): CarBuilder<MakeSet, ModelSet, true> {
this.car.year = year;
return this as any;
}
build(this: CarBuilder<true, true, true>): Car {
return this.car as Car;
}
}This code creates a car object safely by setting all required parts before building.
Typescript
const car = new CarBuilder() .setMake('Toyota') .setModel('Corolla') .setYear(2023) .build();
Sample Program
This program builds a pizza object step-by-step. It requires setting the size and cheese before building. Pepperoni is optional.
Typescript
interface Pizza {
size: string;
cheese: boolean;
pepperoni: boolean;
}
class PizzaBuilder<SizeSet = false, CheeseSet = false> {
private pizza: Partial<Pizza> = {};
setSize(size: string): PizzaBuilder<true, CheeseSet> {
this.pizza.size = size;
return this as any;
}
addCheese(cheese: boolean): PizzaBuilder<SizeSet, true> {
this.pizza.cheese = cheese;
return this as any;
}
addPepperoni(pepperoni: boolean): PizzaBuilder<SizeSet, CheeseSet> {
this.pizza.pepperoni = pepperoni;
return this as any;
}
build(this: PizzaBuilder<true, true>): Pizza {
return this.pizza as Pizza;
}
}
const myPizza = new PizzaBuilder()
.setSize('Large')
.addCheese(true)
.addPepperoni(true)
.build();
console.log(myPizza);OutputSuccess
Important Notes
TypeScript's type system helps catch missing required steps before running the code.
Use Partial<T> to build objects gradually.
Returning this as any is a common trick to change builder state types.
Summary
The type-safe builder pattern helps create objects safely step-by-step.
It uses TypeScript generics to track which parts are set.
This prevents mistakes like missing required properties when building objects.