Skip to main content

TypeScript enums the right way

ยท 3 min read
Ivan Barlog
AWS Solutions Architect @ BeeSolve

Have you ever wondered who might have thought of TypeScript enums? I did because of all the ways how that can go wrong but I actually never found out who has introduced this feature to the language. This article is not about that either.

A good thing about TypeScript enums is that you don't need to use it. If you look into the code of how the enum is implemented you will realize that you don't want to use them.

TypeScript enums - 401 Unauthorized

TypeScript enums - 402 Unauthorized

Can you see how in the first example Unauthorized has value 401 but in the second 402?

This is just one example which makes me scream "Do not use enums in TypeScript. Period."

Below is the alternative to use enum values in the TypeScript.

Firstly you need to specify some array of values which you want to use as enum. Here we are using simple string values. Do not forget to add as const to the end - otherwise this won't work.

Secondly you can create the type out of this array. Here the typeof statusCodes will infer the values of array. Since we've used as const the array will be narrowed to just the literal strings not all the string values. The number in brackets just means that StatusCode type will contain any of the values of the array.

Next follows the helper function which ensures that the value is one of the values in the statusCodes array. This helps the TypeScript to type hint any value - it also validates the data in runtime so you are sure that you are working with correct values.

Lastly you can see the example usage within the handleStatusCode() function. Here we are also using assertUnreachable() helper function which helps you to handle every case for StatusCode type. You should define this function in your helpers file/package and start using it as soon as possible ๐Ÿ™‚

Feel free open this example in the TypeScript playground and play around with it.

In order to find out how this work you can try following things:

  1. try to remove as const and see what type will be inferred in StatusCode
  2. try to add/remove values from statusCodes array
  3. try to remove if statements from handleStatusCode() function
const statusCodes = [
"ok",
"badRequest",
"unauthorized",
"paymentRequired",
"forbidden",
"notFound",
] as const;

type StatusCode = (typeof statusCodes)[number];

function ensureStatusCode(value: any): asserts value is StatusCode {
if (statusCodes.includes(value)) return;

throw Error(
`${value} is not valid StatusCode. Use one of ${statusCodes.join()}`
);
}

function handleStatusCode(code: StatusCode): number {
if (code === "ok") return 200;
if (code === "badRequest") return 400;
if (code === "unauthorized") return 401;
if (code === "paymentRequired") return 402;
if (code === "forbidden") return 403;
if (code === "notFound") return 404;

assertUnreachable(code);
}

function assertUnreachable(value: never): never {
throw Error("An unreachable state reached!");
}

This whole thing is called type narrowing and you can read about it in the official documentation.

Ah and also, if you want to use some cool stuff like --erasableSyntaxOnly in TypeScript so you don't need transpile your code in order to run it within Node.js v23, you shouldn't be using enums anyway.

Good bye enums ๐Ÿ‘‹