We all know, that TypeScript helps us in many ways. The compiler guides us during our work, ensuring, that every piece of data falls into a given place.
But there are some limitations. TypeScript was meant to help us during development time. After the compilation step, we still cannot be 100% sure what can happen during runtime...
Unless we do something about that and defend ourselves against unwanted runtime errors! This talk serves as an introduction to the problem and explains how we can face it to make our applications more error-proof.
Type Safety at Runtime in Typescript
AI Generated Video Summary
TypeScript does not have runtime type checking, but there are libraries available for runtime type validation. ZAD is a popular runtime type validation library in the React ecosystem, offering schema primitives for specific validations on primitive types and support for complex data like objects and arrays. ZAD also provides methods for parsing values and handling successful or failed parsing with error objects.
1. Introduction to TypeScript and Runtime Types
Hello everyone, today I'd like to walk you through a very cool topic around TypeScript, so type safety at runtime. TypeScript is not perfect and will never meet all expectations of all users. In the early stages of TypeScript, there was a question about having a runtime type system, but it was stated that runtime type checking remains outside the design goals. TypeScript compiles down to JavaScript, so there is no runtime validation for types. One potential solution is to use runtime types or validators, and there are multiple libraries available. You can choose a library based on criteria such as performance and popularity. The top five runtime types libraries by GitHub stars are Yap, AJV, Zad, IOTS, and Runtypes.
Hello everyone, today I'd like to walk you through a very cool topic around TypeScript, so type safety at runtime, but before that, I'd like to introduce myself. I'm Kejtan Świątek, I'm a front-end developer from Wrocław, and you can find my work on either Twitter, at kejtansw, or on my blog kejtan.dev, when I occasionally write about front-end functional programming and stuff I learn along the way.
Let me start this presentation by stating that TypeScript is not perfect, and I know it's a pretty bold statement, considering this is a TypeScript conference, and I hope organisers won't ban me for life because of it, but what I mean is that TypeScript, like any other tool, will never meet all expectations of all of its users. And in the early stages of TypeScript, one of the expectations was to have a runtime type system, and the community asked whether is it a missing piece of the TypeScript ecosystem. And this question was answered pretty early and in 2014 it was stated that runtime type checking remains outside of the design goals for TypeScript. And as you can see, it had pretty mixed feelings from the community. But I think after eight years since that answer, this pretty much settled in in our minds as TypeScript developers. And we got pretty much used to the fact that TypeScript underneath is just plain JavaScript. And what I mean by that is that after our work with our code is done, it compiles down to JavaScript to be interpreted by different runtimes like Node.js or our browsers. So we got rid of all our interfaces and typefaces. So we have no runtime validation for our types.
And the simplest example of that may be fetching some data from external APIs. So this is a common code in our TypeScript projects. We have an interface for our runtime data. We fetch this data from some API and parse the response to a JSON payload. But you can see in line 7 that we declared that our runtime value is of type Hero, but the question here is how sure are we that the return object here is really of type Hero? And I can assure you that in this example, we are not 100% sure about that. So what's the potential solution to this problem? This might be runtime types, also known as runtime validators or schema validators or JSON decoders. And what about TypeScript ecosystem? Like with almost every issue, we can either implement it ourselves or use a library for it. And fortunately for runtime types or runtime validators, there are multiple libraries doing just that. And we're choosing a library that suits your needs. There are different criteria that you can use. For example, performance of such parsing. And the best resource for that is called TypeScript runtime type benchmarks. And it's a GitHub repository and you can find it under the QR code here in the corner. And basically it gives you a different statistic around different types of parsing and type assertion, and gives you a performance benchmarks of those for different libraries. And like with every other library, the other criteria you can use for finding the one that suits your needs is popularity. And we all like measuring popularity by GitHub stars. So here you have it, top five runtime types libraries by number of GitHub stars. And those are Yap, AJV, Zad, IOTS, and Runtypes. Here I would like to give you a small preview of one of those libraries.
2. ZAD: Runtime Type Validation Library
ZAD is a runtime type validation library that is often used in the React ecosystem. It provides schema primitives for JavaScript and TypeScript, allowing for specific validations on primitive types like strings and numbers. ZAD also supports creating schemas for complex data like objects and arrays, with the ability to extract inferred types from the created schema. Additionally, ZAD offers combinators for extending and merging object schemas, as well as picking and omitting fields. In practice, ZAD provides methods like parse and SaveParse for parsing values and handling successful or failed parsing with error objects.
And for that I've chose ZAD. And not because it's the most performance one of or most popular, it's just because it comes out pretty often in the React ecosystem and also it has pretty simple API.
Starting from basics. ZAD has something called schema primitives, so primitive schemas for every primitive type of JavaScript and TypeScript like string, number, boolean, even date, or empty type like undefined or null. And from that we can be more specific. For example, create a schema for a string that has a maximum number of characters or or has an email format or URL format, something like that. And for numbers as well, like a number greater than some value or lower than some value or an integer value and many other specific methods for those schema primitives.
We can also create schemas for more complex data like objects or arrays. For example, with the object method, we create our schema for the dog type by passing the desired structure with name and age. But instead of values, we pass the primitive schemas for those fields. What's also cool about Zot is also that we can extract the inferred type out of the created schema, so we can create a type of dog by using the inferred type from Zot. If you are also interested in reusing our schemas, there are some combinators for extending and merging object schemas and for picking and omitting some fields out of the object schema.
How can we use our schemas in practice? So, for example, let's create a schema for a basic string type. Then we can use the parse method to parse our value. So we passed our runtime value and if the parsing is successful, it just returns our value untouched. But if it's failing, it shows a Zot error. But if we don't like to throw errors around, we can use SaveParse method and it returns an object with an information whether the parsing was successful or not. And if it is, it returns our data as a payload or our error object if the parsing fails.
That's all from my side. I gave you all the theory behind runtime validators, I gave you a list of potential solutions and also introduced you to one of it. And now it's your turn to try it out in your project.
Comments