Perform Runtime Type-checks on Primitives and Objects with zod

zod is a powerful library allowing to run runtime type-checks.

It's complementary to TypeScript, which does type checks in compile time.

In this lesson we create few schemas that represent primitive type values, along with zod utilities which make these more precise. We also create object schemas and nest one inside another.

All schemas are being validated against example values. We analyze how to interpret error messages in case of failures.

Share with a coworker

Social Share Links

Send Tweet

Transcript

[00:00] Start by installing Zod using the npm install zod command and importing Zod from the Zod package. Now Zod exports lots of smaller utilities though we're going to access all of them using the Z utility object just for simplicity. Let's now create the first schema. So this is going to be number of guests since all examples within this course are going to relate to a room booking business domain. So what Zod allows us to do is to create a schema.

[00:35] So an expression that will exist in JavaScript will be validated against such a schema to verify whether all conditions are met. So we're going to use the number of guests and run the parse function against an expression. So I'm going to save the file and run this file here using tsnode and running the file and we see no output since this parse function is going to throw an error only in case if the expression passed doesn't meet the schema conditions. But we can of course run console.log and if the value meets the conditions of the schema then it is going to be returned. So we are expecting the value 3 and here we get it.

[01:21] Let's also see how could the validation fail. So if number is expected, then string is not going to be a correct value, which is kind of obvious, but we can also see that there is a long stack trace and there is a ZodError thrown saying that there is an invalid type. The path is empty since this is just a primitive value not an object. We can see that a number was expected, a string was received, and finally the message is expected number, received string with a long stack trace, and again this is the ZodError object. Now let's add more conditions to our schema.

[01:59] So instead of defining what is the type expected, some of the types such as numbers, string, etc. Have more specific expectations. In case of a number, we can expect a minimum value. So let's say that the minimum number of guests in order to make a booking, make a reservation is one. And let's say that the maximum number of guests for a single room is four.

[02:24] So there are some pieces that will not be visible on TypeScript level, but they are possible to be verified within JavaScript's runtime. Here, the value 3, if we make it back a number again, this is going to work. So we will see that there is a console log with value 3. But if we make it 5, then the minimum is being matched, the expectation, however the maximum for is failing and yet again we get a ZodError saying that there is a error code saying the value is too big, maximum for etc etc, number must be less than or equal to 4. And again we get a ZodError object over here.

[03:08] So let's create one more and that would be a contact email which is going to be a Z.string. So we have the equivalents to all JavaScript primitive types in Zod. So that would be number, string, boolean, symbol, bigint, null, undefined, etc. Etc. So they all have their equivalence.

[03:30] So in case of strings, we have way more utility functions such as UUID, URL, and many, many more. We're going to use the email one. So let's say that what we're going to verify is going to be in our case, the contact email dot parse. And let's say that we want to run john dot doe at example dot com and let's see whether this matches the expectations but let's also make this one pass So what we can see is 4 and this one is a correct email. However, if we try to ruin the email format we'll see that yet again we get an error.

[04:14] In this case The error code is the invalid string validation email, etc. Again, the path is empty since this is just a primitive value, not an object property. So let's now move forward to creating a object-based schema. So in order to create it we're going to do a room booking schema and this is going to be a Zod object. So Zod's API is trying to reflect how we write JavaScript or TypeScript as much as it's possible.

[04:48] So let's say that we would have a room type which is going to be a z.string. So now we're allowing all possible strings to define what the room type is. We're going to deal with that later and there would be a due date which is going to be a z.string.date so we can create a special string that is formatted to include a valid date format. Now we can use the number of guests which we've used before which is going to be essentially this line and finally we can have a price which is going to be Z number and yet another helper which is going to be a positive since obviously a price cannot be negative. Now let's create an example object const example booking and this one is going to have room type which is let's say a suit and due date which is let's say 2025 the last day of the year number of guests which is three and finally the price which is let's say 300 And here I just need to close the string and finally room booking schema.

[06:07] So we are running the parse again on the schema itself. And we're validating whether this one meets all our conditions. So, again, let's run the console log to see what is the output. So if everything is correct, then we're going to see that value itself, which is essentially parsed by Zod. So something seems to be wrong, and we can see that there is an invalid type number and an undefined was received, so number of guests.

[06:42] So I made a typo, so it's nice that it has been already figured out. So let's run it again and we can see that there is an object. By the way, if I ruin this object back again, then we can see that the path property has been correctly defined, so that we can see, okay, this is the sequence of the properties that we need to walk into from the very root of the object that is being checked. So let's make this object correct again and let's proceed by creating a nested object so in this case we're creating const guest details schema which is going to be yet another Z object and here we will define that there would be a string property under the name. The age is going to be z.number which is going to be positive or at least zero.

[07:42] And an email which is yet again z.string which is also a valid email. Now what we can do with our above schema if we just move it over here is that we can use one schema within another one. So that's going to be guest details which is going to use a z.array. And the array doesn't compile since there is at least one argument expected. And the argument is another schema of what is the element of the array.

[08:19] So as simple as that. So let's also run this validation. So we will see that the guest details property is missing right now. So let's also see that invalid type expected array received undefined. Yes, details property is required, but it was not passed.

[08:39] So let's add this one. So that would be guest details. And this is expected to be an array. And let's say that there would be one array, name, which is John Doe, age, which is 30. And let's say that the email is the one that we have created before.

[08:58] So John Doe at example.com. So let's see whether it meets our expectations. And as we can see, yes, it does.