patterntypescriptModeratepending
TypeScript branded types for type-safe IDs
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.