0Pricing
TypeScript Academy · Lesson

Strongly-typed builders

Design chainable builders that accumulate typed options; ensure required steps and valid combinations using generics and conditional types.

Strongly-typed builders is a free TypeScript Academy lesson on CoddyKit — lesson 3 of 3. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the TypeScript Academy learning path, one of 3 lessons in the course, and your progress syncs across the web and the CoddyKit app.

Intro

Goal: Build chainable APIs that encode progress in the type. Each step returns a new typed state so invalid orders fail at compile time.

  • Accumulate options in a generic
  • Gate steps with flags
  • Keep DX simple

State flags

Each step returns a new type of state; build is accessible only when the required flags are true.

type Flags = { method: boolean; url: boolean }

type With<F extends Partial<Flags>> = { flags: F }

type True = true

type Builder<F extends Partial<Flags>, O extends Record<string, unknown>> = With<F> & {
  method<M extends "GET" | "POST">(m: M): Builder<F & { method: True }, O & { method: M }>
  url<U extends `/${string}`>(u: U): Builder<F & { url: True }, O & { url: U }>
  build(this: Builder<{ method: True; url: True }, any>): O
}

function createBuilder(): Builder<{}, {}> {
  return {
    flags: {},
    method(m) { return { ...this, flags: { ...this.flags, method: true }, method: this.method, url: this.url, build: this.build } as any },
    url(u) { return { ...this, flags: { ...this.flags, url: true }, method: this.method, url: this.url, build: this.build } as any },
    build() { return this as any }
  } as any
}

const b = createBuilder()
const good = b.method("GET").url("/users").build()   // ok
// const bad = b.build() // error: build requires method & url flags

Accumulate options

Accumulate options in the O generic; each call returns a type that adds new properties.

type B2<F extends Partial<Flags>, O extends Record<string, unknown>> = With<F> & {
  header<K extends string, V extends string>(k: K, v: V): B2<F, O & { headers: Record<K, V> }>
  query<K extends string, V extends string>(k: K, v: V): B2<F, O & { query: Record<K, V> }>
  method<M extends "GET" | "POST">(m: M): B2<F & { method: True }, O & { method: M }>
  url<U extends `/${string}`>(u: U): B2<F & { url: True }, O & { url: U }>
  build(this: B2<{ method: True; url: True }, any>): O
}

function builder2(): B2<{}, {}> { return {} as any }

const conf = builder2()
  .method("POST")
  .url("/login")
  .header("x-id", "42")
  .query("next", "home")
  .build()

Valid combinations

Restrict combinations with conditional types: make the body free only for POST.

type Method = "GET" | "POST"

type BodyIfPost<M extends Method, B> = M extends "POST" ? { body: B } : {}

type B3<F extends Partial<Flags>, O extends Record<string, unknown>> = With<F> & {
  method<M extends Method>(m: M): B3<F & { method: True }, O & { method: M }>
  url<U extends `/${string}`>(u: U): B3<F & { url: True }, O & { url: U }>
  body<B>(b: B & (O extends { method: "POST" } ? unknown : never)): B3<F, O & BodyIfPost<O extends { method: infer M } ? Extract<M, Method> : Method, B>>
  build(this: B3<{ method: True; url: True }, any>): O
}

function builder3(): B3<{}, {}> { return {} as any }

const ok = builder3().method("POST").url("/u").body({ a: 1 }).build()
// const badBody = builder3().method("GET").url("/u").body({ a: 1 }).build() // compile-time error

Variadic pipeline

Gather steps as a collection with a Variadic tuple array; manage input/output with Parameters/ReturnType.

type Step<A extends any[], R> = (...args: A) => R

type Pipeline<P extends Step<any, any>[]> = {
  use<S extends Step<any, any>>(...s: [S]): Pipeline<[...P, S]>
  run<A extends Parameters<P[0]>>(this: Pipeline<P>, ...args: A): ReturnType<P[number]>
}

function pipeMake(): Pipeline<[]> { return {} as any }

const pipe = pipeMake()
  .use((a: string) => a.length)
  .use((n: number) => n % 2 === 0)
// pipe.run("abc") // demo; implementation omitted

Tips

Tips:

Keep state as a phantom generic; don't burden the runtime.

Keep step names small and fixed (method, url).

Separate difficult combinations with conditional types; keep the chain clean.

Builder gating check

Quick check: How do we ensure .method() is called before .build()?

Recap

Recap: Carry typed state along the chain, accumulate options in generics, catch invalid sequences at compile time.

Frequently asked questions

Is the “Strongly-typed builders” lesson free?

Yes — the full text of “Strongly-typed builders” is free to read here on the web, and the TypeScript Academy course includes 3 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the TypeScript Academy course, upgrade to CoddyKit PRO.

What will I learn in “Strongly-typed builders”?

Design chainable builders that accumulate typed options; ensure required steps and valid combinations using generics and conditional types. You practise TypeScript Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.

Do I need any experience to start TypeScript Academy?

No prior experience is required. TypeScript Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 3 of 3, so you can start here or from the beginning and move at your own pace.

How long does the “Strongly-typed builders” lesson take?

Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.

Can I write and run code in this TypeScript Academy lesson?

Yes. Every TypeScript Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.

All lessons in this course

  1. Tuple rest elements, curry types
  2. Compose functions and arguments safely
  3. Strongly-typed builders
← Back to TypeScript Academy