Assemble a complex object step by step, so construction reads clearly instead of one giant constructor call.
`new Request(url, method, headers, body, timeout, retries, null, null, true, false, ...)` is unreadable and error-prone — it's easy to pass arguments in the wrong order, and most callers only need a handful of them set.
A builder exposes one method per optional piece, each returning the builder itself so calls can chain. A final `build()` assembles the immutable result. Only the fields that were actually set get used.
Every `.with...()` call returns `this`, so calls chain naturally. `build()` is the single point where the final object is produced.
class RequestBuilder {
#config = { method: "GET", headers: {}, timeout: 5000 };
withUrl(url) { this.#config.url = url; return this; }
withMethod(method) { this.#config.method = method; return this; }
withHeader(key, value) { this.#config.headers[key] = value; return this; }
withTimeout(ms) { this.#config.timeout = ms; return this; }
build() {
if (!this.#config.url) throw new Error("url is required");
return Object.freeze({ ...this.#config });
}
}
const request = new RequestBuilder()
.withUrl("/api/orders")
.withMethod("POST")
.withHeader("Content-Type", "application/json")
.withTimeout(3000)
.build();
console.log(request);