Lädt...


🔧 Leveraging TypeScript branded types for stronger type checks


Nachrichtenbereich: 🔧 Programmierung
🔗 Quelle: dev.to

Written by Rashedul Alam✏️

Branded types in TypeScript allow us to write code more clearly and provide more type-safe solutions. This powerful feature is also very simple to implement and can help us maintain our code more efficiently.

In this article, we’ll learn how to use branded types effectively in our TypeScript code, starting with a simple example and moving on to some advanced use cases and more. Let’s get started.

What are branded types in TypeScript?

Branded types in TypeScript are very powerful and efficient features for better code readability, context, and type safety code writing. They give an extra definition of our existing type. Thus, we can compare entities with similar structures and file names.

For example, instead of having the user email in the string, we can create a branded TypeScript type for the email address and separate it from a normal string. This enables us to validate our entity in a more organized way and clarifies the code.

Without branded types, we usually store values in generic typed variables, which is typical in most cases. Working with branded types lets us emphasize that variable and maintain its validity across the code.

A simple example of branded TypeScript types

Let’s create a branded type for email addresses. We can create a branded type by adding a brand name to the type. In this case, the branded type for the email address will be the following:

type EmailAddress = string & { __brand: "EmailAddress" }

Here, we have created the branded type EmailAddress by attaching a __brand name "EmailAddress".

Note that there’s no generic syntax to follow when creating branded types. It mainly depends on the generic type of that branded type — string, number, etc.. The syntax __brand is also not a reserved word, which means we can have any variable name other than __brand.

Now, let’s create an object of type EmailAddress and pass a string:

const email: EmailAddress = 'asd'; // error

As we can see, we are getting a type error stating:

Type 'string' is not assignable to type 'EmailAddress.' Type 'string' is not assignable to type '{ __brand: "EmailAddress"; }.'

To fix this, let’s create a basic validation for our email address:

const isEmailAddress = (email: string): email is EmailAddress => {
  return email.endsWith('@gmail.com');
};

Here, instead of returning a boolean, we are returning email is EmailAddress. That means the email is type casted to EmailAddress if the function returns true. This helps us validate the string before performing any operations on it:

const sendVerificationEmail = (email: EmailAddress) => {
  //...
}
const signUp = (email: string, password: string) => {
  //...
  if (isEmailAddress(email)) {
    sendVerificationEmail(email) // pass
  }
  sendVerificationEmail(email) // error
}

As we can see, the error is not visible inside the if condition, but occurs outside that condition's scope.

We can also use assert to validate the email address. This can sometimes be useful if we want to throw an error if the validation doesn't pass:

function assertEmailAddress(email: string): asserts email is EmailAddress {
  if (!email.endsWith('@gmail.com')) {
    throw new Error('Not an email addres');
  }
};

const sendVerificationEmail = (email: EmailAddress) => {
  //...
};

const signUp = (email: string, password: string) => {
  //...
  assertEmailAddress(email);
  sendVerificationEmail(email); // ok
};

Here, as we can see, we are stating asserts email is EmailAddress as the returned type to the function. This ensures that if the validation passes, the email address is of the branded type EmailAddress.

More advanced uses for branded types in TypeScript

The example above is a simple demonstration of the branded type. We can use it in more advanced cases as well. Let’s see an example.

First, let’s declare a common Branded type, which we can attach to other types:

declare const __brand: unique symbol
type Brand<B> = { [__brand]: B }
export type Branded<T, B> = T & Brand<B>

Here, a unique symbol is used in branded types to create a unique brand that distinguishes one type from another. It's a symbol that is guaranteed to be unique. This means that each time you create a new unique symbol, it will be distinct from any other symbol. Here's an example to illustrate this:

// Define unique symbols to use as brands
const metersSymbol: unique symbol = Symbol("meters");
const kilometersSymbol: unique symbol = Symbol("kilometers");

// Define branded types
type Meters = number & { [metersSymbol]: void };
type Kilometers = number & { [kilometersSymbol]: void };

// Helper functions to create branded values
function meters(value: number): Meters {
  return value as Meters;
}

function kilometers(value: number): Kilometers {
  return value as Kilometers;
}

// Variables with branded types
const distanceInMeters: Meters = meters(100);
const distanceInKilometers: Kilometers = kilometers(1);

// The following assignments will cause type errors
const wrongDistance: Meters = distanceInKilometers;
const anotherWrongDistance: Kilometers = distanceInMeters;

// Correct usage
const anotherDistanceInMeters: Meters = meters(200);
const anotherDistanceInKilometers: Kilometers = kilometers(2);

console.log(distanceInMeters, distanceInKilometers);

Having a common Branded type interface allows us to create multiple branded types in TypeScript simultaneously, reducing code implementation and making the code much cleaner. Now, expanding on our above email validation example, we can use this common Branded type to define the EmailAddress brand like so:

type EmailAddress = Branded<string, 'EmailAddress'>;

const isEmailAddress = (email: string): email is EmailAddress => {
  return email.endsWith('@gmail.com');
};

const sendEmail = (email: EmailAddress) => {
  // ...
};

const signUp = (email: string, password: string) => {
  if (isEmailAddress(email)) {
    // send verification email
    sendEmail(email);
  }
};

We can now use this Branded type to create a new branded TypeScript type. Let’s look at another example of using the Branded type. Let’s say we are writing a function to allow a user to like a post. We can use the Branded type in both our userId and postId:

type UserId = Branded<string, 'UserId'>;
type PostId = Branded<string, 'PostId'>;

type User = {
  userId: UserId;
  username: string;
  email: string;
};

type Post = {
  postId: PostId;
  title: string;
  description: string;
  likes: Like[];
};

type Like = {
  userId: UserId;
  postId: PostId;
};

const likePost = async (userId: UserId, postId: PostId) => {
  const response = await fetch(`/posts/${postId}/like/${userId}`, {
    method: 'post',
  });
  return await response.json();
};

// fake objects
const user: User = {
  userId: "1" as UserId,
  email: "[email protected]",
  username: "User1"
}
const post: Post = {
  postId: "2" as PostId,
  title: "Sample Title",
  description: "Sample post description",
  likes: []
}

likePost(user.userId, post.postId) // ok
likePost(post.postId, user.userId) // error

Working with branded types in TypeScript 5.5-beta

With the new TypeScript 5.5-beta release, TypeScript’s control flow analysis can now track how the type of a variable changes as it moves through the code.

This means the types of variables can change based on the code logic, and TypeScript tracks the variable type in each modification chain of any code logic. If there are two possible types of a variable, we can split the types of the variable by applying the necessary condition.

Let’s look at the following code for better understanding:

interface ItemProps {
    // ...
}

declare const items: Map<string, ItemProps>;

function getItem(id: string) {
  const item = items.get(id);  // item has a declared type of ItemProps | undefined
  if (item) {
    // item has type ItemProps inside the if statement
  } else {
    // item has type undefined here.
  }
}

function getAllItemsByIds(ids: string[]): ItemProps[] {
    return ids.map(id => items.get(id)).filter(item => item !== undefined) // Previously we would get error: Type '(ItemProps | undefined)[]' is not assignable to type 'ItemProps[]'. Type 'ItemProps | undefined' is not assignable to type 'ItemProps'. Type 'undefined' is not assignable to type 'ItemProps'
}

Let’s look at the following example. In this example, we get a list of email addresses and store the validated email addresses in the database. Using the previous example, we can create a branded EmailAddress type and store the validated emails in the database:

type EmailAddress = Branded<string, 'EmailAddress'>;
const isEmailAddress = (email: string): email is EmailAddress => {
  return email.endsWith('@gmail.com');
};
const storeToDb = async (emails: EmailAddress[]) => {
  const response = await fetch('/store-to-db', {
    body: JSON.stringify({
      emails,
    }),
    method: 'post',
  });
  return await response.json();
};
const emails = ['[email protected]', '[email protected]', '...'];
const validatedEmails = emails.filter((email) => isEmailAddress(email));
storeToDb(validatedEmails); // error

Here, we can see that, although we are listing all the validated email addresses in the validatedEmails array, we are getting an error while passing inside the storeToDb function. This error is especially visible if we use a TypeScript version prior to v5.5-beta.

In lower TypeScript types, the type of the validatedEmails array is derived from the original variable, the emails array. That’s why we are getting the types of validatedEmails array as string[]. However, this issue is fixed in the current TypeScript beta version (5.5-beta as of today).

In the current beta version, the validatedEmails are automatically typecasted to EmailAddress[] after we filter the validated emails. So, we will not see the error in the TypeScript 5.5-beta version. To install the TypeScript beta version in our project, run the following command in the terminal:

npm install -D typescript@beta

Conclusion

Branded types are handy features in TypeScript. They provide runtime type safety to ensure code integrity and better readability. They are also very useful for reducing bugs in our code by throwing errors at a domain level.

We can easily validate our branded types and use the validated objects safely across our projects. We can also use the latest TypeScript beta version features to leverage our coding experience more smoothly.

LogRocket: Full visibility into your web and mobile apps

LogRocket Signup

LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.

In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.

Try it for free.

...

🔧 Leveraging TypeScript branded types for stronger type checks


📈 93.61 Punkte
🔧 Programmierung

🔧 Mastering Type Guards in TypeScript: Ensuring Safe Type Checks


📈 42.08 Punkte
🔧 Programmierung

📰 Python Type Hinting: From Type Aliases To Type Variables and New Types


📈 37.42 Punkte
🔧 AI Nachrichten

🔧 Leveraging Mapped Types in TypeScript: Practical Applications


📈 34.6 Punkte
🔧 Programmierung

🔧 Optimizing a TypeScript Curry Function: From Static Types to Variadic Types


📈 32.62 Punkte
🔧 Programmierung

🔧 How Types Work in TypeScript – Explained with JavaScript + TypeScript Code


📈 31.8 Punkte
🔧 Programmierung

🔧 How to Choose Your Type: TypeScript Interfaces vs Types


📈 30.23 Punkte
🔧 Programmierung

🔧 🛠️ Branded Types


📈 30.02 Punkte
🔧 Programmierung

🔧 🛠️ Branded Types


📈 30.02 Punkte
🔧 Programmierung

🔧 I made "TypeScript Swagger Editor", new type of Swagger UI writing TypeScript code in the browser


📈 29.4 Punkte
🔧 Programmierung

🔧 Solving Potential Issues with Type Aliases Using Type Unions and Literal Types


📈 28.66 Punkte
🔧 Programmierung

📰 How Far Back Do Background Checks and Criminal History Checks Go?


📈 28.49 Punkte
🖥️ Betriebssysteme

📰 Work-related Background Checks Vs Employment Background Checks


📈 28.49 Punkte
🖥️ Betriebssysteme

📰 Malware again checks into Hyatt's hotels, again checks out months later with victims' credit cards


📈 28.49 Punkte
📰 IT Security Nachrichten

🔧 Type ✔ Vs Interface ❌: Why you should chose type over interface in typescript.


📈 27.84 Punkte
🔧 Programmierung

🔧 Getting Started with TypeScript: Type Annotations & Type Inference (Part I)


📈 27.84 Punkte
🔧 Programmierung

🔧 Typescript type grouping a union type of objects by any property discriminating these objects.


📈 27.84 Punkte
🔧 Programmierung

🔧 Type Narrowing vs Type Casting in TypeScript


📈 27.84 Punkte
🔧 Programmierung

🔧 Typescript: best type checking for the best type safety


📈 27.84 Punkte
🔧 Programmierung

🔧 Typescript Generate Full Path Type And Get Value Type Of Nested Object


📈 27.84 Punkte
🔧 Programmierung

🔧 Mastering TypeScript Functions: Your Guide to Stronger, Safer Code


📈 27.47 Punkte
🔧 Programmierung

🕵️ CVE-2022-45122 | Movable Type 7/Type Premium/Type Premium Advanced cross site scripting


📈 26.27 Punkte
🕵️ Sicherheitslücken

🕵️ CVE-2022-43660 | Movable Type 7/Type Premium/Type Premium Advanced os command injection


📈 26.27 Punkte
🕵️ Sicherheitslücken

🕵️ CVE-2022-45113 | Movable Type 7/Type Premium/Type Premium Advanced URL input validation


📈 26.27 Punkte
🕵️ Sicherheitslücken

📰 Why one type of B2B software is poised to emerge stronger from the economic downturn


📈 25.9 Punkte
📰 IT Security Nachrichten

🔧 Use i18n with TypeScript checks


📈 24.57 Punkte
🔧 Programmierung

🔧 Leveraging TypeScript for domain-driven design


📈 23.45 Punkte
🔧 Programmierung

🔧 Released 'kura' - Store constraints for Data::Checks, Type::Tiny, Moose and more.


📈 23 Punkte
🔧 Programmierung

🔧 Frontend made fully type-safe and without null checks. Part 1


📈 23 Punkte
🔧 Programmierung

⚠️ Windows Credential Guard Insufficient Checks On Kerberos Encryption Type Use


📈 23 Punkte
⚠️ PoC

⚠️ [dos] - OS X Kernel - OOB Read of Object Pointer Due to Insufficient Checks in Raw Cast to enum Type


📈 23 Punkte
⚠️ PoC

⚠️ [dos] - OS X Kernel - OOB Read of Object Pointer Due to Insufficient Checks in Raw Cast to enum Type


📈 23 Punkte
⚠️ PoC

🔧 PHP 8 News: Union Types and Mixed Types


📈 22.3 Punkte
🔧 Programmierung

🔧 Understanding Value Types and Reference Types in C#


📈 22.3 Punkte
🔧 Programmierung

🔧 Infer Types to Avoid Explicit Types


📈 22.3 Punkte
🔧 Programmierung

matomo