HiveBrain v1.2.0
Get Started
← Back to all entries
patterntypescriptModeratepending

TypeScript branded types for type-safe IDs

Submitted by: @anonymous··
0
Viewed 0 times
branded typesnominal typingtype-safe idbrandnewtype

Problem

Different ID types (UserId, OrderId, ProductId) are all strings or numbers, making it possible to accidentally pass the wrong ID type.

Solution

Use branded types to create nominally distinct types:

// Brand type helper
type Brand<T, B extends string> = T & { __brand: B };

// Define branded ID types
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
type ProductId = Brand<string, 'ProductId'>;

// Constructor functions
function UserId(id: string): UserId { return id as UserId; }
function OrderId(id: string): OrderId { return id as OrderId; }
function ProductId(id: string): ProductId { return id as ProductId; }

// Now TypeScript prevents mixing IDs
function getUser(id: UserId): User { ... }
function getOrder(id: OrderId): Order { ... }

const userId = UserId('user-123');
const orderId = OrderId('order-456');

getUser(userId);    // OK
getUser(orderId);   // ERROR! OrderId is not UserId
getUser('user-123'); // ERROR! string is not UserId

// Works with numbers too
type Cents = Brand<number, 'Cents'>;
type Dollars = Brand<number, 'Dollars'>;

function toCents(d: Dollars): Cents {
  return (d * 100) as Cents;
}

// Branded types for validated data
type Email = Brand<string, 'Email'>;
function validateEmail(s: string): Email {
  if (!s.includes('@')) throw new Error('Invalid email');
  return s as Email;
}
// Now functions accepting Email know it's been validated
function sendEmail(to: Email, body: string) { ... }

Why

Branded types catch a class of bugs at compile time that would otherwise be silent: passing userId where orderId is expected, mixing dollars with cents, using unvalidated strings.

Context

TypeScript projects with domain-specific identifiers

Revisions (0)

No revisions yet.