HOME > ARTICLES > TYPESCRIPT PATTERNS FOR LARGE CODEBASES

TYPESCRIPT PATTERNS FOR LARGE CODEBASES

[2024.01.22]  08_MIN_READ

Introduction

TypeScript’s type system is expressive enough to encode most business logic constraints at compile time. This article covers patterns that pay dividends at scale.

Branded Types

Prevent mixing semantically different primitives:

type UserId = string & { readonly __brand: 'UserId' };
type OrderId = string & { readonly __brand: 'OrderId' };

function brandAs<T>(value: string): T {
  return value as unknown as T;
}

const userId = brandAs<UserId>('usr_123');
const orderId = brandAs<OrderId>('ord_456');

// TypeScript error: Argument of type 'OrderId' is not assignable to parameter of type 'UserId'
processUser(orderId);

Discriminated Unions for State Machines

type AsyncState<T> =
  | { status: 'idle' }
  | { status: 'loading' }
  | { status: 'success'; data: T }
  | { status: 'error'; error: Error };

Template Literal Types

type EventName = `on${Capitalize<string>}`;
type HttpMethod = 'GET' | 'POST' | 'PUT' | 'DELETE';
type ApiRoute = `/${string}`;
type ApiEndpoint = `${HttpMethod} ${ApiRoute}`;

Conclusion

These patterns add compile-time safety with minimal runtime overhead. The investment in types pays off when refactoring large codebases.

< /articles