Typescript's Identifier types
Here I explain a concept I found using Typescript where one can use string literal types and types union to create custom unique types.
This is particularly useful for identifiers, which we (hopefuly) never embed into the code but are rather read from an external source and passed to another component untouched.
Let's say we have two kind of entities in our program: User and Product, and both have a property id of type ObjectId. Nothing prevents us to accidentally pass a user ID where a product ID is expected, only a runtime error that would scare our users and steal our peace of mind. By creating and using different types for such IDs (like UserId and ProductId) we can use Typescript's power to ensure we never mix them accidentally.
This was the approach I took in a tool to create Discord bots with Deno called [Denord][1], having different kinds of IDs such as [GuildId][2], [ChannelId][3] and [MessageId][4].
// any string would work
type UserId = 'my-random-string-for-UserId';
type ProductId = 'my-random-string-for-ProductId';
function getUser(id: UserId) {}
function getProduct(id: ProductId) {}
const userId = '' as UserId;
const productId = '' as ProductId;
getUser(userId);
getUser(productId); // error
getUser(productId as UserId); // error
getProduct(productId);
getProduct(userId); // error
getProduct(userId as ProductId); // errorUpdate July 2022: Zack took this one step further by creating a libray that hides the ugliness of this approach https://github.com/modfy/nominal
Update August 2023: This is becoming a common pattern called branded types now.
Update February 2024: Branded types now has a simpler snippet to be used. source
// if you don't want simple strings to be compatible
// remove the question mark here -> V
export type Branded<T, K extends string> = T & { __opaque__?: K };
type UserId = Branded<string, 'User'>;
type ProductId = Branded<string, 'Product'>;
function getUser(userId: UserId) {}
const userId = '12' as UserId;
const productId = '34' as ProductId;
const random = 'hi'; // string
getUser(userId); // works
getUser(productId); // Type '"Product"' is not assignable to type '"User"'
getUser('hello'); // works because of the question mark
getUser('hello' as UserId); // works
getUser(random); // works because of the question mark
getUser(random as UserId); // works