https://www.kravchyk.com/adding-type-safety-to-object-ids-typescript/ Skip to content Kravchyk's Menu Menu * About Adding type safety to object IDs in TypeScript January 29, 2024 by Maciej Kravchyk A code snippet demonstrating type safety of IDs in TypeScript I was creating a type for an options object of a function, one of the options properties would accept an ID of the item (string based UUID) or a special value, i.e. 'currentNode'. Initially I would come up with something like this: interface InsertOptions { // Insert into the item by id or the active item target: string | 'currentNode' } function insert(options: InsertOptions): void { // } insert({ target: 'activeNode' }); // Passing a clearly unintended option value will not result in an error Code language: TypeScript (typescript) Try It There are 2 problems with this approach: * No autotype hints for currentNode * No type checking for the special option since curentNode is a also just a string. type NodeId = string would hardly help since it's just an alias for string and is the same from type checking perspective. One way to solve this would be to wrap the special option in an object: interface InsertOptions { target: string | { target: 'activeNode'} } function insert(options: InsertOptions): void { // } insert({ target: 'KQER3XVBdD3u' }); // Valid insert({ target: { target: 'activeNode' } }); // Valid insert({ target: { target: 'currentNode' } }); // ErrorCode language: TypeScript (typescript) Try It This is going to work, both type checking and autoexpand suggestion will work, but it does not look nice at all... Fortunately TypeScript has template literal types since version 4.1. It's possible to use them to create a type that only an ID would have, like: type NodeId = `node_${string}` Code language: JavaScript (javascript) So now we can do: interface InsertOptions { target: NodeId | 'activeNode' } function insert(options: InsertOptions): void { // } insert({ target: 'activeNode' }); // Valid insert({ target: 'node_KQER3XVBdD3u' }); // Valid insert({ target: 'currentNode' }); // ErrorCode language: TypeScript (typescript) Try It Now the target type is not string anymore and TS can see the difference between the ID and the special options, meaning that the special options are type checked and will show in autotype suggestions. This does not only solve the 2 problems mentioned, but also introduces type safety of IDs in TypeScript. Each type of item can have a different type of ID assigned, I.e. type UserId = `user_${string}`; type GroupId = `group_${string}`; function updateUser(id: UserId): void { // } // Imagine somewhere in the code you have an id variable that is a GroupId declare const id: GroupId; updateUser(id); // Error: Argument of type '`group_${string}`' is not assignable to parameter of type '`user_${string}`'Code language: JavaScript (javascript) Try It I have been using it for a year now and implementing type safe IDs has already prevented me a few times already from mixing entity types. The benefit of this is not just at compile time, but also at runtime. You can use this to detect the type of object by checking its ID prefix and it's also very convenient that whenever the object pops up in the logs, you immediately know what it is just by looking at the ID. Categories Development TypeScript and NPM package.json exports the 2024 way Copyright (c) 2024 Kravchyk.com