Lesson Description

The "User Schema Exercise" Lesson is part of the full, Fullstack TypeScript, v2 (feat. Zod) course featured in this preview video. Here's what you'd learn in this lesson:

Students are instructed to add type safety through schema validation to an Express API server project with Zod.

Preview
Close

Transcript from the "User Schema Exercise" Lesson

[00:00:00]
>> Steve Kinney: And so we've go the thematic piece in here where they are now shared. What I want you to do is take a break from me talking for a moment. In exercises, there's a little user API which is very similar to what we are doing just on the express level, right?

[00:00:15]
Play with the types, play with some schemas, try to get it working. And then we will then do some there, flesh out the rest of this, and to start to look at ways to make this more sustainable and easier to do, and more scalable. Scalable not in the performance sense, but scalable in the sense of, you can do this repeatedly and consistently, right, across a team.

[00:00:41]
So where you are going is in the exercises folder. There is a little user API, which is, there's no database. It's a very simplified version of what we saw earlier. So in exercises, user API, the world's simplest little version of what we were just working with. For you, just try it out in a clean space.

[00:01:04]
There are some tests to test your assumptions if you really want to, you don't have to run them. They're mostly to make sure I didn't break stuff as I was preparing, but if you wanna run them, they're there for you too. But just try to, again, these will start out being all sorts of weird nies.

[00:01:18]
Just take a pass without copy and pasting, just try to get some basic safety in this. And we'll look at it together, and we'll apply it to the larger project.
>> Steve Kinney: Okay, so we had a version, not just similar to what we're working on before, but a nice little, clean safe space with no databases or any fun things like that, just like a little in memory store.

[00:01:49]
And we wanna kinda take the same approach that I just did in our larger app and just kick it around a little bit, right? Cuz we will talk about more sophisticated things in terms of generation. But arguably, if you get this down, you are on the 80/20 principle of, 20% of working, 80% there, you're there.

[00:02:09]
Watch the rest, it's good, you'll love it, but spiritually. So we can do that trick that we saw earlier where if I already have the type and I wanna go back the other direction, we can do that as well. And so I could say something like a UserSchema is z.object.

[00:02:34]
And we'll get there with the UUID, don't steal my thunder. An I is a string, not yet, let's pull in dot as well. The name is a string problem when you got that auto complete. What is stuff where you already have the answers on another branch? And then we could do satisfies the user type.

[00:03:06]
So now I can be pretty confident that this does, in fact, do everything that this type does, right? Generally speaking, cuz this question came up before, I do tend to start this way and go that way, unless the type pre-exists, and I'm trying to match it. So we'll start there.

[00:03:25]
And so now what we can do is say, for instance, for this request body, right? This is for a post one, we might not have the Id just yet. So we could also make the versions. And again, you can do these in line. If you are only gonna use it once, maybe that makes total sense.

[00:03:47]
If you find that you're gonna use a partial version all the time, make it a reusable one, right? It doesn't really matter. I will probably make types out of them, like if this is a larger code base, so I'm going to keep it like this. So yeah, basically, again, there's a do not repeat yourself part of keeping this dry, right?

[00:04:08]
If you did make these all bespoke, then you have to keep them in sync. If you derive one from the other, then you should be in a much better situation. Cool, and so that should do the trick, the order there doesn't really matter, just like it doesn't in TypeScript.

[00:04:23]
You can omit the key that you don't want, to make the rest of them partial. You can make them all partial and take out the key you don't want. That's how math works, you get to the same result. Great, and so now we can just go through here, and we can say, okay, this body for creating a new user.

[00:04:41]
Well, that should be the CreateUserSchema.parse, right? And now everything is what I think it ought to be, right? Theoretically, we will never actually hit this because this will throw. In the catch is when I could have chosen to then look at the errors and figure out what they're missing.

[00:05:07]
And we saw before, you can do stuff like,
>> Steve Kinney: It's a dot message, we can also, I think,
>> Steve Kinney: So on and so forth. You can begin to also customize the messages, which are nice for the response types and stuff like that. So now we know this is the thing that it ought to be in both of these cases.

[00:05:36]
A new user, again, we wanna say, this should be the full one at this point. And again, if this is a database, we might choose as it came out of the database. This is like our overly simple example, so we can focus on the core thing. And so now I could theoretically, like one, if this didn't match, I would be getting yelled at, right, cuz we now know the type of both things.

[00:06:01]
But then also this is somewhat unnecessary cuz we know that it's a user in the same way. Cool, the only advantage you get from this one, by the way, to be clear, is if you like that more than this, choose whichever one makes you happy. Honestly, for smaller ones, this makes me happier cuz I don't remember what a user is, but we all know how extreme this can get, right?

[00:06:32]
If it's a giant one, maybe I just. If it's a primitive thing that I work with all the time and it happens to be giant, I will go for the other approach, would give me the shorthand. Because again, you can do stuff like.
>> Steve Kinney: We click on newUser, we end up here.

[00:06:47]
I can go to that definition, which is nice as well, right? Trade-offs, you choose which one ever makes you happier. But now we know that this is newUser. It can very cleanly be pushed onto here, it's not any, I get a lot more type safety. Everything worked beforehand because it just happened to work.

[00:07:05]
Now it works cuz I know that it works, cuz all the types match up. Because before, everything was just any, and my tests are the only thing keeping everything together, right? Cuz now theoretically, the testing story becomes a lot easier, even if you're only working on just the client or just the server.

[00:07:24]
Because beforehand, right, I would have to have a bunch of tests, make sure I got back the 400 whatever. At this point, I could just send in an object and should not throw, or should throw, or response should be 200 or not a 200. And I will know that the rest of the type safety was there.

[00:07:45]
Again, so query is another one. So this is for filtering. So in this case, any of these can be optional, right? So do I have?
>> Steve Kinney: You can argue that I could reuse this one. So we'll say,
>> Steve Kinney: And so this should be, everything's optional, but there should not be an Id in there.

[00:08:15]
So now I could say, PartialUser.parse, with the query. And again, now this is a string undefined, this is a string undefined, right? We've got the ability to filter them. We know whatever thing ought to be at this point. Yeah, everything's basically typed, say, there's not any to found in here.

[00:08:43]
So we should be pretty good. We know that's a string, great.
>> Steve Kinney: Right, we can get rid of those little helpful things. Cuz again, this is gonna cast it as a string, regardless if it is one, right? Cuz it was any, and we wanted to call to lowercase on it.

[00:09:03]
Get rid of all of that, we're still good, not a red line to be found on that minimap, right? Everything is type safe, even if things are coming in from the outside world. The params, that could just be, again, we could either make one, or we could say that it is just either in UserSchema.pick id is true.

[00:09:38]
>> Steve Kinney: We don't get a lot from that one cuz that was a string to begin with, but we'll see where it becomes useful in a second. So then users, great, awesome, super. Again, this is where, again, I might choose to then
>> Steve Kinney: Make it its own thing.
>> Steve Kinney: Ordering matters in JavaScript when it doesn't in TypeScript.

[00:10:12]
You could also go the other way where I could make this NID and then I could extend it. Six of one, half dozen of the other. Great, and so where was I?
>> Steve Kinney: We got the users. Req body is a problem, but in this case, we got that.

[00:10:42]
>> Steve Kinney: Boom, everything's what it should be. And then delete is, let's see, we've just got that id.
>> Steve Kinney: And you're like, hey, Steve, I am bored watching you with four CRUD endpoints. Yes, we're getting somewhere, I'm building towards something here, I promise. Great, index, should we find the index?

[00:11:21]
>> Steve Kinney: See, look, type safety, helping me in ways that it wasn't going to a second ago. Great, so on and so forth. Cool, everything is what I think it should be. All right, this basically lets you run the tests or run the actual server. So we've got that in place, and now I've got this little tiny, little CRUD endpoint is now completely type-safed up, right?

[00:11:47]
We now know that everything is what we think it is, or it will blow up, right? And that point, we would implement some logic to catch the error, send the 400, right? So it is slightly too express-specific, and just kind of next to the core themes that we should be talking about that we're not gonna do it, but theoretically there.

[00:12:08]
And I could begin to refine some of these a little bit, right? This is gonna be more than just the TypeScript part. I will now make sure the email is actually an email, right? Could I have found an email RegX over stack overflow like I have done for years?

[00:12:23]
Sure, but will this also do the trick? If we wanna make sure this is a UUID, right, we can get both of those as well. And now no red lines in my mini map, right? So now we can also above and beyond, cuz it still satisfies that type.

[00:12:39]
And this is where I might flip some of the logic, so if you wanna see.
>> Steve Kinney: All right, so there are six of one that have done the other, but I had the extra safety of knowing this will actually be a UUID, this will actually be an email, so on and so forth.

[00:12:59]
And I basically get that for free with a grand total of what, I don't know how to count parentheses, 6, 7, 12 extra characters. Count the dots, 14 characters, not bad, right, I'll take it. That all of a sudden, I look like a 10X engineer at work. All right, great, so now we have a totally effectively type-safed API in place.

[00:13:24]
Again, with more validation than we would've had normally. Everything else that extends from this works, right? So now the big question is, how do we do this again at scale?

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now