Three methods for typing strings in Typescript
How to use Enums, Union Types and Const Assertions to reduce runtime errors for strings.

One of the key benefits of using Typescript is the ability to define strict type rules for your variables, which can help catch bugs and ensure that your code is predictable and easy to understand. This is particularly useful for strings, which can be tricky to work with in Typescript because they can be assigned any value at runtime and are subject to spelling errors by the developer.
By defining strict rule types, we can catch bugs at compile-time. For example, if we try to assign a value to a variable that is not a member of the ruleset, which is obviosuly handy for reducing runtime errors in your code.
In this blog post, we will explore three different ways of defining string rules in Typescript: enums, union types, and const assertions (for pattern matching). We will look at each of these approaches in turn, discussing their pros and cons and providing examples to illustrate how they can be used in practice.
Enums
The first way of defining string rules in Typescript is to use enums - a way of defining a set of named constants, where each constant has a corresponding numeric value. For example, here's how you might define an enum for a set of possible time values:
enum Time {
Morning = 'morning',
Midday = 'midday',
Evening = 'evening',
}
Once you have defined an enum, you can use it to specify the type of a variable. For example, the following code defines a variable called myTime that can only be assigned a value from the Time enum:
let time: Time;
time = Time.Morning; // OK
time = Time.Midday; // OK
time = Time.Evening; // OK
time = Time.Midnight; // Error
Enums can be useful for defining a set of possible values for a variable, but they have some limitations. For example, they can only be used to define a set of string values, and they can't be used to define a set of string values that are not known at compile-time.
Personally, I find enums to be a bit cumbersome to use, and I prefer to use union types instead, which we'll explore now.
Union Types
Another way to define rules for strings in Typescript is through union types. A union type allows a variable to be one of several types, rather than just a single type. For example, the following code defines a greeting variable that can be either a string or a number:
let time = string | number;
This means that the time variable can either be a string, such as "morning", or a number, such as 12. The advantage of using union types is that it allows for more flexibility in the type of data that can be stored in a variable.
Following the same example above, we can use a union type to define a variable that can be assigned a value from the Time enum, or a string that is not known at compile-time:
type Time = 'morning' | 'midday' | 'evening';
let time: Time;
time = 'morning'; // OK
time = 'midday'; // OK
time = 'evening'; // OK
time = 'midnight'; // Error
I prefer this method as it's terser and more flexible than enums. However, there is an enhancement we can make to this method to make it even stricter and allow for pattern matching, which we'll explore now.
Const Assertions
The final method we'll look at for defining string rules in Typescript is through const assertions. A const assertion is a way of telling Typescript that a variable is a constant, which means that it can't be reassigned. For example, the following code defines a variable called time
that can only be assigned a value matching the Time const assertion. It's quite long and verbose, but it's very strict and allows for pattern matching (source):
const hours = [
'00',
'01',
'02',
'03',
'04',
'05',
'06',
'07',
'08',
'09',
'10',
'11',
'12',
'13',
'14',
'15',
'16',
'17',
'18',
'19',
'20',
'21',
'22',
'23',
'24',
] as const;
const minutes = [
'00',
'01',
'02',
'03',
'04',
'05',
'06',
'07',
'08',
'09',
'10',
'11',
'12',
'13',
'14',
'15',
'16',
'17',
'18',
'19',
'20',
'21',
'22',
'23',
'24',
'25',
'26',
'27',
'28',
'29',
'30',
'31',
'32',
'33',
'34',
'35',
'36',
'37',
'38',
'39',
'40',
'41',
'42',
'43',
'44',
'45',
'46',
'47',
'48',
'49',
'50',
'51',
'52',
'53',
'54',
'55',
'56',
'57',
'58',
'59',
] as const;
type HH = typeof hours[number];
type MM = typeof minutes[number];
type Time = `${HH}:${MM}`;
const time: Time = '12:00'; // OK
const time: Time = '12:60'; // Error
That's it! I hope you found this post useful. If you have any questions or comments, reach out to me on Twitter.