Skip to main content

Builder Pattern 🏗️

Definition: The Builder pattern separates the construction of complex objects from their representation, allowing the same construction process to create different representations.

🎯 Intent

Construct complex objects step by step. The pattern allows you to produce different types and representations of an object using the same construction code.

🤔 Problem

Imagine creating a complex object like a House. A house might have walls, doors, windows, a roof, a garage, a swimming pool, a garden, etc. The simple approach would be to create a huge constructor with all possible parameters:

// This becomes unwieldy quickly
class House {
constructor(walls, doors, windows, roof, garage, pool, garden, basement, attic) {
// Too many parameters!
}
}

This approach has several problems:

  • Most parameters will be optional, leading to telescoping constructors
  • The constructor becomes hard to read and maintain
  • Creating different variations becomes cumbersome

💡 Solution

The Builder pattern suggests extracting the object construction code out of its own class and move it to separate objects called builders.

The pattern organizes object construction into a set of steps. To create an object, you execute a series of these steps on a builder object. The important part is that you don't need to call all steps - you can call only those steps that are necessary for producing a particular configuration of an object.

🏗️ Structure

Director
├── construct(builder)
└── setBuilder(builder)

Builder (interface)
├── reset()
├── buildPartA()
├── buildPartB()
├── buildPartC()
└── getResult()

ConcreteBuilder implements Builder
├── product: Product
├── reset(): void
├── buildPartA(): void
├── buildPartB(): void
├── buildPartC(): void
└── getResult(): Product

Product
└── parts[]

💻 Code Example

Basic Implementation

// Product class
class House {
constructor() {
this.parts = [];
}

addPart(part) {
this.parts.push(part);
}

describe() {
console.log(`🏠 House with: ${this.parts.join(', ')}`);
return this.parts;
}
}

// Abstract Builder
class HouseBuilder {
constructor() {
this.reset();
}

reset() {
this.house = new House();
return this;
}

// Abstract methods
buildFoundation() {
throw new Error('buildFoundation() must be implemented');
}
buildWalls() {
throw new Error('buildWalls() must be implemented');
}
buildRoof() {
throw new Error('buildRoof() must be implemented');
}
buildDoors() {
throw new Error('buildDoors() must be implemented');
}
buildWindows() {
throw new Error('buildWindows() must be implemented');
}

getResult() {
const result = this.house;
this.reset(); // Reset for next construction
return result;
}
}

// Concrete Builder - Modern House
class ModernHouseBuilder extends HouseBuilder {
buildFoundation() {
console.log('🏗️ Building concrete slab foundation');
this.house.addPart('Concrete slab foundation');
return this;
}

buildWalls() {
console.log('🧱 Building glass and steel walls');
this.house.addPart('Glass and steel walls');
return this;
}

buildRoof() {
console.log('🏠 Building flat roof with solar panels');
this.house.addPart('Flat roof with solar panels');
return this;
}

buildDoors() {
console.log('🚪 Installing smart glass doors');
this.house.addPart('Smart glass doors');
return this;
}

buildWindows() {
console.log('🪟 Installing floor-to-ceiling windows');
this.house.addPart('Floor-to-ceiling windows');
return this;
}

addSmartHome() {
console.log('🤖 Installing smart home system');
this.house.addPart('Smart home automation system');
return this;
}

addPool() {
console.log('🏊 Adding infinity pool');
this.house.addPart('Infinity pool');
return this;
}
}

// Concrete Builder - Traditional House
class TraditionalHouseBuilder extends HouseBuilder {
buildFoundation() {
console.log('🏗️ Building stone foundation');
this.house.addPart('Stone foundation');
return this;
}

buildWalls() {
console.log('🧱 Building brick walls');
this.house.addPart('Brick walls');
return this;
}

buildRoof() {
console.log('🏠 Building pitched roof with tiles');
this.house.addPart('Pitched tile roof');
return this;
}

buildDoors() {
console.log('🚪 Installing wooden doors');
this.house.addPart('Wooden doors');
return this;
}

buildWindows() {
console.log('🪟 Installing traditional windows');
this.house.addPart('Traditional windows');
return this;
}

addFireplace() {
console.log('🔥 Adding stone fireplace');
this.house.addPart('Stone fireplace');
return this;
}

addGarden() {
console.log('🌹 Adding cottage garden');
this.house.addPart('Cottage garden');
return this;
}
}

// Director class
class HouseDirector {
constructor() {
this.builder = null;
}

setBuilder(builder) {
this.builder = builder;
}

// Different construction algorithms
constructBasicHouse() {
if (!this.builder) throw new Error('Builder not set');

console.log('🏗️ Constructing basic house...');
return this.builder.buildFoundation().buildWalls().buildRoof().buildDoors().buildWindows();
}

constructLuxuryHouse() {
if (!this.builder) throw new Error('Builder not set');

console.log('🏗️ Constructing luxury house...');
this.constructBasicHouse();

// Add luxury features based on builder type
if (this.builder instanceof ModernHouseBuilder) {
this.builder.addSmartHome().addPool();
} else if (this.builder instanceof TraditionalHouseBuilder) {
this.builder.addFireplace().addGarden();
}

return this.builder;
}
}

// Usage
function demonstrateBuilder() {
console.log('=== Builder Pattern Demo ===\n');

const director = new HouseDirector();

// Build modern houses
console.log('Building Modern Houses:');
console.log('-'.repeat(30));

const modernBuilder = new ModernHouseBuilder();
director.setBuilder(modernBuilder);

// Basic modern house
director.constructBasicHouse();
const basicModernHouse = modernBuilder.getResult();
basicModernHouse.describe();

console.log();

// Luxury modern house
director.constructLuxuryHouse();
const luxuryModernHouse = modernBuilder.getResult();
luxuryModernHouse.describe();

console.log('\n' + '='.repeat(50) + '\n');

// Build traditional houses
console.log('Building Traditional Houses:');
console.log('-'.repeat(30));

const traditionalBuilder = new TraditionalHouseBuilder();
director.setBuilder(traditionalBuilder);

// Basic traditional house
director.constructBasicHouse();
const basicTraditionalHouse = traditionalBuilder.getResult();
basicTraditionalHouse.describe();

console.log();

// Luxury traditional house
director.constructLuxuryHouse();
const luxuryTraditionalHouse = traditionalBuilder.getResult();
luxuryTraditionalHouse.describe();
}

demonstrateBuilder();

🌟 Real-World Examples

1. SQL Query Builder

// Product - SQL Query
class SQLQuery {
constructor() {
this.selectFields = [];
this.fromTable = '';
this.joinClauses = [];
this.whereConditions = [];
this.groupByFields = [];
this.havingConditions = [];
this.orderByFields = [];
this.limitCount = null;
this.offsetCount = null;
}

toString() {
let query = '';

// SELECT clause
if (this.selectFields.length > 0) {
query += `SELECT ${this.selectFields.join(', ')}`;
}

// FROM clause
if (this.fromTable) {
query += ` FROM ${this.fromTable}`;
}

// JOIN clauses
if (this.joinClauses.length > 0) {
query += ` ${this.joinClauses.join(' ')}`;
}

// WHERE clause
if (this.whereConditions.length > 0) {
query += ` WHERE ${this.whereConditions.join(' AND ')}`;
}

// GROUP BY clause
if (this.groupByFields.length > 0) {
query += ` GROUP BY ${this.groupByFields.join(', ')}`;
}

// HAVING clause
if (this.havingConditions.length > 0) {
query += ` HAVING ${this.havingConditions.join(' AND ')}`;
}

// ORDER BY clause
if (this.orderByFields.length > 0) {
query += ` ORDER BY ${this.orderByFields.join(', ')}`;
}

// LIMIT clause
if (this.limitCount !== null) {
query += ` LIMIT ${this.limitCount}`;
}

// OFFSET clause
if (this.offsetCount !== null) {
query += ` OFFSET ${this.offsetCount}`;
}

return query + ';';
}
}

// Builder
class QueryBuilder {
constructor() {
this.reset();
}

reset() {
this.query = new SQLQuery();
return this;
}

select(...fields) {
if (fields.length === 0) {
this.query.selectFields.push('*');
} else {
this.query.selectFields.push(...fields);
}
console.log(`📊 SELECT: ${fields.join(', ') || '*'}`);
return this;
}

from(table) {
this.query.fromTable = table;
console.log(`📋 FROM: ${table}`);
return this;
}

join(table, condition, type = 'INNER') {
const joinClause = `${type} JOIN ${table} ON ${condition}`;
this.query.joinClauses.push(joinClause);
console.log(`🔗 JOIN: ${joinClause}`);
return this;
}

leftJoin(table, condition) {
return this.join(table, condition, 'LEFT');
}

rightJoin(table, condition) {
return this.join(table, condition, 'RIGHT');
}

where(condition) {
this.query.whereConditions.push(condition);
console.log(`🔍 WHERE: ${condition}`);
return this;
}

groupBy(...fields) {
this.query.groupByFields.push(...fields);
console.log(`📊 GROUP BY: ${fields.join(', ')}`);
return this;
}

having(condition) {
this.query.havingConditions.push(condition);
console.log(`🔍 HAVING: ${condition}`);
return this;
}

orderBy(field, direction = 'ASC') {
const orderClause = `${field} ${direction}`;
this.query.orderByFields.push(orderClause);
console.log(`📈 ORDER BY: ${orderClause}`);
return this;
}

limit(count) {
this.query.limitCount = count;
console.log(`🔢 LIMIT: ${count}`);
return this;
}

offset(count) {
this.query.offsetCount = count;
console.log(`⏭️ OFFSET: ${count}`);
return this;
}

build() {
const result = this.query;
this.reset();
return result;
}
}

// Usage examples
console.log('=== SQL Query Builder Demo ===\n');

const queryBuilder = new QueryBuilder();

// Simple query
console.log('Building Simple Query:');
console.log('-'.repeat(25));
const simpleQuery = queryBuilder
.select('name', 'email')
.from('users')
.where('active = 1')
.orderBy('name')
.build();

console.log('📝 Generated Query:');
console.log(simpleQuery.toString());

console.log('\n' + '='.repeat(40) + '\n');

// Complex query with joins
console.log('Building Complex Query:');
console.log('-'.repeat(25));
const complexQuery = queryBuilder
.select('u.name', 'u.email', 'p.title', 'COUNT(c.id) as comment_count')
.from('users u')
.leftJoin('posts p', 'p.user_id = u.id')
.leftJoin('comments c', 'c.post_id = p.id')
.where('u.active = 1')
.where('p.published = 1')
.groupBy('u.id', 'p.id')
.having('comment_count > 5')
.orderBy('comment_count', 'DESC')
.limit(10)
.build();

console.log('📝 Generated Query:');
console.log(complexQuery.toString());

2. HTTP Request Builder

// Product - HTTP Request
class HTTPRequest {
constructor() {
this.method = 'GET';
this.url = '';
this.headers = {};
this.queryParams = {};
this.body = null;
this.timeout = 5000;
this.retries = 0;
this.authToken = null;
}

getConfig() {
const config = {
method: this.method,
url: this.buildUrl(),
headers: this.headers,
timeout: this.timeout,
};

if (this.body) {
config.data = this.body;
}

return config;
}

buildUrl() {
let url = this.url;
const paramString = Object.keys(this.queryParams)
.map((key) => `${key}=${encodeURIComponent(this.queryParams[key])}`)
.join('&');

if (paramString) {
url += (url.includes('?') ? '&' : '?') + paramString;
}

return url;
}

async execute() {
console.log(`🌐 Executing ${this.method} request to: ${this.buildUrl()}`);
console.log(`📋 Headers:`, JSON.stringify(this.headers, null, 2));

if (this.body) {
console.log(`📦 Body:`, JSON.stringify(this.body, null, 2));
}

// Simulate HTTP request
return new Promise((resolve) => {
setTimeout(() => {
resolve({
status: 200,
data: { message: 'Request successful', url: this.buildUrl() },
headers: { 'content-type': 'application/json' },
});
}, 100);
});
}
}

// Builder
class HTTPRequestBuilder {
constructor() {
this.reset();
}

reset() {
this.request = new HTTPRequest();
return this;
}

// HTTP methods
get(url) {
this.request.method = 'GET';
this.request.url = url;
console.log(`🔍 GET: ${url}`);
return this;
}

post(url) {
this.request.method = 'POST';
this.request.url = url;
console.log(`📤 POST: ${url}`);
return this;
}

put(url) {
this.request.method = 'PUT';
this.request.url = url;
console.log(`📝 PUT: ${url}`);
return this;
}

delete(url) {
this.request.method = 'DELETE';
this.request.url = url;
console.log(`🗑️ DELETE: ${url}`);
return this;
}

// Headers
header(name, value) {
this.request.headers[name] = value;
console.log(`📋 Header: ${name} = ${value}`);
return this;
}

headers(headerObj) {
Object.assign(this.request.headers, headerObj);
console.log(`📋 Headers:`, headerObj);
return this;
}

contentType(type) {
return this.header('Content-Type', type);
}

accept(type) {
return this.header('Accept', type);
}

// Authentication
bearerAuth(token) {
this.request.authToken = token;
return this.header('Authorization', `Bearer ${token}`);
}

basicAuth(username, password) {
const credentials = btoa(`${username}:${password}`);
return this.header('Authorization', `Basic ${credentials}`);
}

// Query parameters
query(name, value) {
this.request.queryParams[name] = value;
console.log(`🔍 Query param: ${name} = ${value}`);
return this;
}

queryParams(params) {
Object.assign(this.request.queryParams, params);
console.log(`🔍 Query params:`, params);
return this;
}

// Body
body(data) {
this.request.body = data;
console.log(`📦 Body set:`, typeof data === 'object' ? '[Object]' : data);
return this;
}

json(data) {
this.contentType('application/json');
return this.body(JSON.stringify(data));
}

form(data) {
this.contentType('application/x-www-form-urlencoded');
const formData = Object.keys(data)
.map((key) => `${key}=${encodeURIComponent(data[key])}`)
.join('&');
return this.body(formData);
}

// Configuration
timeout(ms) {
this.request.timeout = ms;
console.log(`⏱️ Timeout: ${ms}ms`);
return this;
}

retries(count) {
this.request.retries = count;
console.log(`🔄 Retries: ${count}`);
return this;
}

// Build and execute
build() {
const result = this.request;
this.reset();
return result;
}

async execute() {
const request = this.build();
return await request.execute();
}
}

// Usage examples
async function demonstrateHTTPBuilder() {
console.log('=== HTTP Request Builder Demo ===\n');

const builder = new HTTPRequestBuilder();

// Simple GET request
console.log('Building Simple GET Request:');
console.log('-'.repeat(30));

const getResponse = await builder
.get('https://api.example.com/users')
.header('User-Agent', 'MyApp/1.0')
.query('page', 1)
.query('limit', 10)
.timeout(3000)
.execute();

console.log('✅ Response:', getResponse);

console.log('\n' + '='.repeat(50) + '\n');

// Complex POST request
console.log('Building Complex POST Request:');
console.log('-'.repeat(30));

const postResponse = await builder
.post('https://api.example.com/users')
.bearerAuth('eyJhbGciOiJIUzI1NiIs...')
.accept('application/json')
.json({
name: 'John Doe',
email: 'john@example.com',
role: 'user',
})
.timeout(5000)
.retries(2)
.execute();

console.log('✅ Response:', postResponse);

console.log('\n' + '='.repeat(50) + '\n');

// Form submission
console.log('Building Form POST Request:');
console.log('-'.repeat(30));

const formResponse = await builder
.post('https://api.example.com/contact')
.form({
name: 'Jane Smith',
email: 'jane@example.com',
message: 'Hello World!',
})
.execute();

console.log('✅ Response:', formResponse);
}

// Run the demo
demonstrateHTTPBuilder();

3. Configuration Builder

// Product - Application Configuration
class AppConfig {
constructor() {
this.database = {};
this.server = {};
this.logging = {};
this.security = {};
this.cache = {};
this.email = {};
this.features = {};
}

validate() {
const errors = [];

// Validate required database settings
if (!this.database.host) errors.push('Database host is required');
if (!this.database.name) errors.push('Database name is required');

// Validate server settings
if (!this.server.port) errors.push('Server port is required');

if (errors.length > 0) {
throw new Error(`Configuration validation failed:\n- ${errors.join('\n- ')}`);
}

return true;
}

toJSON() {
return {
database: this.database,
server: this.server,
logging: this.logging,
security: this.security,
cache: this.cache,
email: this.email,
features: this.features,
};
}

toString() {
return JSON.stringify(this.toJSON(), null, 2);
}
}

// Builder
class ConfigBuilder {
constructor() {
this.reset();
}

reset() {
this.config = new AppConfig();
return this;
}

// Database configuration
database(options) {
Object.assign(this.config.database, options);
console.log('🗄️ Database config:', options);
return this;
}

mysql(options = {}) {
return this.database({
type: 'mysql',
port: 3306,
...options,
});
}

postgresql(options = {}) {
return this.database({
type: 'postgresql',
port: 5432,
...options,
});
}

mongodb(options = {}) {
return this.database({
type: 'mongodb',
port: 27017,
...options,
});
}

// Server configuration
server(options) {
Object.assign(this.config.server, options);
console.log('🖥️ Server config:', options);
return this;
}

port(port) {
this.config.server.port = port;
console.log(`🔌 Server port: ${port}`);
return this;
}

host(host) {
this.config.server.host = host;
console.log(`🌐 Server host: ${host}`);
return this;
}

// Logging configuration
logging(options) {
Object.assign(this.config.logging, options);
console.log('📝 Logging config:', options);
return this;
}

logLevel(level) {
this.config.logging.level = level;
console.log(`📊 Log level: ${level}`);
return this;
}

logFile(filename) {
this.config.logging.file = filename;
console.log(`📄 Log file: ${filename}`);
return this;
}

// Security configuration
security(options) {
Object.assign(this.config.security, options);
console.log('🔒 Security config:', options);
return this;
}

jwtSecret(secret) {
this.config.security.jwtSecret = secret;
console.log('🔑 JWT secret configured');
return this;
}

cors(options = {}) {
this.config.security.cors = {
enabled: true,
origin: '*',
methods: ['GET', 'POST', 'PUT', 'DELETE'],
...options,
};
console.log('🌐 CORS configured:', this.config.security.cors);
return this;
}

// Cache configuration
cache(options) {
Object.assign(this.config.cache, options);
console.log('💾 Cache config:', options);
return this;
}

redis(options = {}) {
return this.cache({
type: 'redis',
host: 'localhost',
port: 6379,
...options,
});
}

memcached(options = {}) {
return this.cache({
type: 'memcached',
host: 'localhost',
port: 11211,
...options,
});
}

// Email configuration
email(options) {
Object.assign(this.config.email, options);
console.log('📧 Email config set');
return this;
}

smtp(options) {
return this.email({
type: 'smtp',
...options,
});
}

// Feature flags
features(options) {
Object.assign(this.config.features, options);
console.log('🎛️ Features configured:', options);
return this;
}

enableFeature(feature, enabled = true) {
this.config.features[feature] = enabled;
console.log(`🎛️ Feature ${feature}: ${enabled ? 'enabled' : 'disabled'}`);
return this;
}

// Environment-specific configurations
development() {
return this.logLevel('debug')
.cors({ origin: 'http://localhost:3000' })
.enableFeature('debug', true)
.enableFeature('hotReload', true);
}

production() {
return this.logLevel('error')
.cors({ origin: 'https://myapp.com' })
.enableFeature('debug', false)
.enableFeature('monitoring', true)
.enableFeature('compression', true);
}

testing() {
return this.logLevel('warn')
.database({ name: 'test_db' })
.enableFeature('debug', true)
.enableFeature('testMode', true);
}

build() {
const result = this.config;
result.validate(); // Validate before returning
this.reset();
return result;
}
}

// Usage examples
function demonstrateConfigBuilder() {
console.log('=== Configuration Builder Demo ===\n');

const builder = new ConfigBuilder();

// Development configuration
console.log('Building Development Configuration:');
console.log('-'.repeat(35));

const devConfig = builder
.postgresql({
host: 'localhost',
name: 'myapp_dev',
username: 'dev_user',
password: 'dev_pass',
})
.port(3000)
.host('localhost')
.development()
.redis({ host: 'localhost' })
.smtp({
host: 'localhost',
port: 1025,
secure: false,
})
.jwtSecret('dev-secret-key')
.build();

console.log('✅ Development Config:');
console.log(devConfig.toString());

console.log('\n' + '='.repeat(60) + '\n');

// Production configuration
console.log('Building Production Configuration:');
console.log('-'.repeat(35));

const prodConfig = builder
.mysql({
host: 'prod-db.example.com',
name: 'myapp_prod',
username: 'prod_user',
password: process.env.DB_PASSWORD || 'secure-password',
ssl: true,
})
.port(8080)
.host('0.0.0.0')
.production()
.redis({
host: 'redis.example.com',
password: process.env.REDIS_PASSWORD || 'redis-password',
})
.smtp({
host: 'smtp.gmail.com',
port: 587,
secure: true,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD,
},
})
.jwtSecret(process.env.JWT_SECRET || 'super-secure-secret')
.build();

console.log('✅ Production Config:');
console.log(prodConfig.toString());

console.log('\n' + '='.repeat(60) + '\n');

// Testing configuration
console.log('Building Test Configuration:');
console.log('-'.repeat(30));

const testConfig = builder
.mongodb({
host: 'localhost',
name: 'myapp_test',
})
.port(3001)
.testing()
.logFile('test.log')
.enableFeature('mockData', true)
.build();

console.log('✅ Test Config:');
console.log(testConfig.toString());
}

// Run the demo
demonstrateConfigBuilder();

✅ Pros

  • Step-by-Step Construction: Allows you to construct objects step by step
  • Different Representations: Can create different representations of the same object
  • Improved Readability: Builder methods provide a more readable way to construct objects
  • Parameter Validation: Can validate parameters at each step
  • Immutability: Can create immutable objects by building them completely before returning
  • Flexibility: Easy to add new construction steps without changing existing code

❌ Cons

  • Code Complexity: Increases code complexity due to multiple new classes
  • Memory Overhead: Creates additional objects during construction
  • Learning Curve: More complex pattern to understand initially
  • Overkill for Simple Objects: Unnecessary for objects with few parameters

🎯 When to Use

  • Complex Objects: When creating objects with many optional parameters
  • Step-by-Step Construction: When object creation involves multiple steps
  • Different Representations: When you need to create different representations of the same object
  • Immutable Objects: When building immutable objects with many fields
  • Configuration Objects: For creating configuration objects with many optional settings

🔄 Variations

1. Fluent Interface Builder

class FluentBuilder {
method1(value) {
this.value1 = value;
return this; // Return this for chaining
}

method2(value) {
this.value2 = value;
return this;
}
}

2. Step Builder

class StepBuilder {
constructor() {
return {
step1: (value) => ({
step2: (value) => ({
build: () => new Product(value1, value2),
}),
}),
};
}
}

3. Generic Builder

class GenericBuilder {
constructor(ProductClass) {
this.ProductClass = ProductClass;
this.properties = {};
}

set(property, value) {
this.properties[property] = value;
return this;
}

build() {
return new this.ProductClass(this.properties);
}
}
  • Abstract Factory: Builders often use factories to create components
  • Composite: Builder can be used to build Composite structures
  • Singleton: Builder can be implemented as a Singleton
  • Strategy: Different builders can represent different strategies for object construction

📚 Further Reading