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
, having different kinds of IDs such as GuildId
, ChannelId
and MessageId
.
// 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); // error
Update 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.