Treat zod Schemas as Single Source of Truth

In this lesson we learn that the zod schemas become the single source of truth.

We shall not update TypeScript types manually, since they are generated automatically from the schema.

Same with validation functions.

Whenever the schema changes, all the rest will update accordingly. It's an important mind shift.

Code
Share with a coworker

Social Share Links

Send Tweet

Transcript

[00:00] Whenever working with runtime validation libraries, it's important to realize that it's the schema which becomes the single source of truth for both the compile time types and the runtime validation functions. So typically, Whenever we create a TypeScript type or an interface, it's a type that determines what are the valid objects or other expressions or primitive types that we're using throughout our program. And whenever we update the type or the interface, we might need to update the according objects or other expressions within our codebase. However, in this case, it's the schema which should be updated as the first one and both the types and the validation functions will update automatically. So there is nothing that we should do here.

[00:48] So we have already seen that if we use the room booking schema from the previous lesson and we run parse against an object, basically whenever our schema changes, the validation function would update accordingly. Now, how about the type? So we can extract the type of a certain entity, of a certain type, interface, object, whatever that is, just by using z.infare. And what we need to pass as the time parameter here is the schema, obviously. However, due to TypeScript syntax, what we need to do is to extract the type from this runtime expression.

[01:28] And this is a runtime expression since this is basically a variable, whether it's const, let or var, doesn't matter, so we simply need to wrap it with the typeof keyword and we've got the type and so our room booking is simply a room type, due date, number of guests and the price. So what we can do now is to create an example const booking object which is not just going to be an object literal that is unrelated to any typescript type, interface or class or whatever but we're going to annotate it with the room booking type that we have just inferred from our Zod schema and as we can see since this is just an empty literal S for now this object is missing the following required properties, which is essentially all the four room type, due date, number of guests, and the price. And generally speaking, it's not about the missing properties, but it's about pretty much all the features and all the capabilities that TypeScript is able to perform in the compile time. So according to the type that has been inferred, TypeScript is capable to run all the compile time validations. And as we can see, some of these utility features are being lost.

[02:47] For instance, the price is a number, but not just any number, but a positive one. And as we can see, the fact that this is a positive number is lost because there is simply no clear way how to determine a positive number, at least according to TypeScript. And the same here. There is a minimum value one and maximum value four. And again, simply there is no clear way to define such a range in TypeScript, or at least no simple, no clear, no built in way in TypeScript.

[03:18] And so some of this information could be lost. However, the fact that this is a number is still quite important and this solves a lot of bugs anyway. So we could treat that the vast majority, we could say the Pareto principle, 80% of the important features are still being translated into TypeScript and the minority, let's say 20%, could be lost. So let's quickly fill in this object. So this is a booking which includes the room type, due date, number of guests, and the price.

[03:51] Now it's pretty obvious that this booking object is going to pass the validation. However, if it happens that our object, our expression is missing one of the properties or the property value is of an invalid type or anything like that and for some reason somewhere in our codebase there is an unsafe type operation applied such as the as any type assertion. Now it could happen that our expression and the TypeScript type itself is out of sync and TypeScript for some reason is just unable to find where does this error come from. And this is exactly where the runtime validation is super useful. So let's run this file and we will see that certainly the validation function is able to find out that there is simply a missing property, that this room type property on the object should be a string but is undefined.

[04:54] Now it's easy to figure out that since we have an any over here then there could be bugs and TypeScript is not capable of finding this. But what is important is to figure out what are these certain cases. And this is what we're going to do in the next lesson.