scalable systems handle growth without breaking
← back · 2 min read ·

template literal types, in practice

the route string and the params object should not be allowed to disagree.

template literal types let typescript treat a string type as a pattern it can read, instead of an opaque value it skips over. they shipped in typescript 4.1 and for two years i used them for nothing more interesting than `on${Capitalize<E>}` in a component library. then a typed router landed in our codebase and i finally understood what they were for.

the bug they fix

every router in every codebase has this shape:

function link(path: string, params: Record<string, string>) { /* ... */ }

link("/users/:id/orders/:orderId", { id: "u_1" }); // missing orderId. runtime crash.

the path string and the params object are two unrelated values. the compiler has no idea they’re supposed to agree. you find out when a customer hits a 404.

the pattern: extract params from the path

type ExtractParams<P extends string> =
  P extends `${string}:${infer Param}/${infer Rest}`
    ? Param | ExtractParams<`/${Rest}`>
    : P extends `${string}:${infer Param}`
      ? Param
      : never;

type Params<P extends string> = { [K in ExtractParams<P>]: string };

function link<P extends string>(path: P, params: Params<P>) { /* ... */ }

link("/users/:id/orders/:orderId", { id: "u_1" });
// ^ Property 'orderId' is missing

that’s the whole move. the path is no longer a string the compiler skips over — it’s a value the compiler reads. autocomplete on the params object now lists id and orderId. rename :id to :userId and every call site lights up red. the link between the two values is enforced by the type system, not by a comment or a code review.

where else it earns its keep

the cost

template literal types run inside the type checker. they have limits.

a path string and a params object should not be allowed to disagree. a stringly-typed event name and its handler shouldn’t either. a css variable that doesn’t exist shouldn’t typecheck. template literal types are the smallest tool in the typescript box that makes any of those true.

scalable labs·cvr 30091604·github·linkedin·hello@scalable.dk