The builder pattern in TypeScript is used to give a more flexible solution to creating complex objects, by breaking the construction up into separate steps. An example of this is the Promise class, which you may be familiar with.

The builder pattern is useful for building complex objects. It lets you break up construction into separate steps, rather than one big constructor. It’s especially useful when there are a lot of steps, and some of those steps can be optional. You can use the construction steps you need, and skip the ones you don’t.

A good real-world example of this would be building a computer:

interface Computer {
    constructor();
    constructor(cpu: CPU);
    constructor(cpu: CPU, ram: RAM, storage: Storage);
    constructor(cpu: CPU, storage: Storage);
    constructor(cpu: CPU, gpu: GPU);
}

If you tried to think of this like a class, you might have problems writing a constructor for it. There are a lot of options, and not all of them are necessary. For example, there are a lot of different storage options for a computer, and there are a lot of optional parts, like graphics cards and network cards.

This is where the builder pattern steps in, breaking up the construction into discrete steps. The builder version may look something like this:

const myComputer = newComputer().addGPU(myGPU).addRAM(corsair).finish();

Example 1

Here’s the interface for our builder:

interface Builder<T> {
    value: T;
    then(next: (val: T) => T): Builder<T>;
    finally(): T;
}

The aim is to be able to build up a similar functionality to a promise, where we can chain together steps like:

const value = new Builder().then()
.then()
.finally();

I’ve made my interface more generic, like a Promise we have a .then() function that takes a function as a parameter. Some alternative implementations might have fixed methods attached to the class, and you can choose one or a combination of both.

Here’s an example implementation for this, which allows you to store and modify a string.

class StringBuilder implements Builder<string> {

    value: string;

    then(next: (val: string) => string): StringBuilder{
        return new StringBuilder(next(this.value));
    }

    finally(): string {
        return this.value;
    }

    constructor(value: string) {
        this.value = value;
    }
}

We store the actual string in value, and our class is sort of a box around the variable.

Our then() function takes a function as a parameter; any function that takes a string as a parameter and returns a new string builder with that as the value.

Our finally() function acts as a getter function, we can use that to get our string value.
These two functions are what give us our builder syntax, since calling a builder method gives us back an object with the same methods, which in our case is just .then().

Here’s some usage of this StringBuilder:

const myName = 'charles spencer chaplin     ';

const myString = new StringBuilder(myName)
    .then((str) => str.trim()) //Anonymous function
    .then(capitalize) //Already defined function
    .finish(); //To get the new value

console.log(myString); //Charles Spencer Chaplin

Example 2 – More Traditional

Let’s implement a more traditional builder example, using an RPG character as an example. Here are our types:


type CharType = 'healer' | 'attacker' | 'tank';

type Weapon = {
    bestClass: CharType;
    attack: number;
};

class Character {
    type: CharType;
    weapon: Weapon;
    health = 100;
    defense = 10;
    attack = 10;
}

The values don’t matter too much, the important part is that we have a character with a lot of options, and we might not know which ones we want to configure.

Here’s our CharacterBuilder class:

class CharacterBuilder {
    private char: Character;

    setType(newType: CharType) {
        this.char.type = newType;
    }

    setWeapon(weapon: Weapon) {
        this.char.weapon = weapon;
    }

    setHealth(health: number) {
        this.char.health = health;
    }

    setDefense(defense: number) {
        this.char.defense = defense;
    }

    setAttack(attack: number) {
        this.char.attack = attack;
        if (this.char.type === 'healer' && attack > 20) attack = 20;
    }

    constructor() {
        this.char = new Character();
    }

    getCharacter() {
        return this.char;
    }
}

We have the actual character we’re creating as a private member, some methods to build some of the values, and our final getter function to get our character at the end.

We can then use our builder like this:

const builder = new CharacterBuilder();

builder.setType('healer');
builder.setAttack(50);
builder.setDefense(10);
builder.setWeapon({ bestClass: 'healer', attack: 5 });

const myChar = builder.getCharacter();
console.log(myChar);

/*
Character {
  type: 'healer',
  weapon: { bestClass: 'healer', attack: 5 },
  health: 100,
  defense: 10,
  attack: 50
}
 */

We can break up our construction into separate steps, only including the ones we need. These steps are more simplified, in real life, you might have something more complex like getting the weapon from a database.

Conclusion

Hopefully, this was a straightforward introduction to the Builder pattern in TypeScript. Using these examples you can take the code and modify it to your needs. Let me know if you have any questions or suggestions in the comments below, or if you just liked the article, feel free to let me know that as well!

Avatar photo
👋 Hey, I'm Omari Thompson-Edwards
Hey, I'm Omari! I'm a full-stack developer from the UK. I'm currently looking for graduate and freelance software engineering roles, so if you liked this article, reach out on Twitter at @marile0n

💬 Leave a comment

Your email address will not be published. Required fields are marked *

We will never share your email with anyone else.