API Design in Node.js, v5

Validation Middleware

Scott Moss
Netflix
API Design in Node.js, v5

Lesson Description

The "Validation Middleware" Lesson is part of the full, API Design in Node.js, v5 course featured in this preview video. Here's what you'd learn in this lesson:

Scott demonstrates using Zod middleware to validate request bodies, handle errors, and enforce input checks before route handlers, improving code clarity and preventing unexpected data issues.

Preview
Close

Transcript from the "Validation Middleware" Lesson

[00:00:00]
>> Scott Moss: One of the big things that we that I always do when I make an API is I like to validate the inputs, OK? So before I run my middleware, before I run my handler specifically, I want to protect. I don't want to have to be in my handlers doing a bunch of if checks if this exists, if this, if this, if this, like I just want to assume that everything I need for this handler to run is, has already been validated and given. So the best way I can do that is to validate the inputs that are passed to those handlers, so I don't have to write all those if statements.

[00:00:19]
So the best way I can do that is to validate the inputs that are passed to those handlers, so I don't have to write all those if statements. And the best way to do that in my opinion is the right middleware for it, so that's what we'll do, we'll write some middleware to validate some inputs. For the handlers and Controllers that we'll be writing later. So what we can do is we can create some middleware helper functions that can take in a Zod schema, any Zod schema, and then what it'll do is it'll Depending on that function, whether it's validating the body, the parameters, or the or the query.

[00:00:35]
So what we can do is we can create some middleware helper functions that can take in a Zod schema, any Zod schema, and then what it'll do is it'll Depending on that function, whether it's validating the body, the parameters, or the or the query. It'll validate that object against the schema and it will log the errors, If there are any very similar to what we did in the .env file, so. Let's do that. So I'm gonna go to my source, I'm gonna make a new folder here, call it Middleware.

[00:00:51]
So I'm gonna go to my source, I'm gonna make a new folder here, call it Middleware. Like that, make a new file, Validation. That's yes. And I'm going to import.

[00:01:09]
And I'm going to import. Some types from Express because they're all gonna be types I can just say import type like that. I'm not sure if you could do that. You just say import type because these are all just gonna be types.

[00:01:23]
You just say import type because these are all just gonna be types. I want the request. I want the response. I want the next function.

[00:01:45]
I want the next function. So it's just type scripts you don't need to have it, but it just makes it easier. And then I'm gonna import some types or import some things from Zodd. This will be the Zod.

[00:01:56]
This will be the Zod. Zo schema, wait is this one deprecated now deprecated use Zoy also use Zod schema since it's still here, and then Zod. Error, there we go. Or they already removed the marks so the title and lesbian poison.

[00:02:12]
Or they already removed the marks so the title and lesbian poison. Oh yeah, they just want me to do time, OK, cool. There we go. All right, so the first one we're gonna do is the one where we validate.

[00:02:29]
All right, so the first one we're gonna do is the one where we validate. A body, so this is a payload that you send up on like a put or a post request so we want to validate the body so the request our body. So we will export, cons, validate body, and this is gonna be a middleware that takes in some option. The option is really just the schemema that you want to.

[00:02:45]
The option is really just the schemema that you want to. Enforce, right? And because this is a higher order function as in it's a function that returns a function, we need to return a middleware function. Remember, a middleware function is just a function that takes in three arguments Request response, and next.

[00:03:02]
Remember, a middleware function is just a function that takes in three arguments Request response, and next. So we're gonna return a function that returns that has those three things as an argument. So we'll say oops, putting it right here. So here's the request, which is type request.

[00:03:19]
So here's the request, which is type request. Here's the response, which is type response. And then here's the next function, which is type next function. Right, we're gonna try to catch this because.

[00:03:39]
Right, we're gonna try to catch this because. Zod will throw an error if any of this fails, and we don't want that to break our server. We have to handle all errors. In our server, otherwise.

[00:03:54]
In our server, otherwise. It'll die. So Tricatch is a great way to do that it's a start. It's not gonna save you from everything, but it's a start.

[00:04:09]
It's not gonna save you from everything, but it's a start. So first, let's try to validate this, so we'll say validate the body. Validated data equals the schema. Parse and we wanna pass in direct.

[00:04:24]
Parse and we wanna pass in direct. Body. Cool, that's the first thing we wanna do. If that's good to go, we'll just add it right back to the body.

[00:04:39]
If that's good to go, we'll just add it right back to the body. We'll say cool, right, the body is gonna equal whatever the validated body is or. The validated data, validated. Data.

[00:05:02]
Data. The reason we reassign it back is because. Maybe this schema has like defaults on it, maybe it has coercions on it that modify the body, so we don't, and the result of parsing that schema would be. It would include those modifications so we would want to attach that to the body to really be truthful to the schema, whereas if we just, we could just call next here and not attached to the body because the body that's on there was already validated, but then if again if that schema had coercions or defaults on it would not have those modifications and then you would run into your expectations will not line up with what was actually happening.

[00:05:14]
It would include those modifications so we would want to attach that to the body to really be truthful to the schema, whereas if we just, we could just call next here and not attached to the body because the body that's on there was already validated, but then if again if that schema had coercions or defaults on it would not have those modifications and then you would run into your expectations will not line up with what was actually happening. So we want to reattach it. And then we wanna call next with no errors cause hey, we're good, and that's the happy path, the sad path. Is oh, there's an error, right?

[00:05:37]
Is oh, there's an error, right? So we wanna say same thing we did before instance of that error. We want to return. In this case, we do want to return a res status of 400 as in that request.

[00:05:52]
In this case, we do want to return a res status of 400 as in that request. You messed up. Let's send back adjacent object to tell you where you messed up. We could say error.

[00:06:14]
We could say error. This will be a validation. Failed, we can call it whatever we want, nothing, no wrong answer there, details. We're gonna say error.

[00:06:36]
We're gonna say error. Or issues, not errors, since we're in the latest version of Zo. Get the error. And then we're gonna put the field, which would be error.

[00:06:55]
And then we're gonna put the field, which would be error. Path. Oh On the dock. And then we'll get the message, which is error message.

[00:07:15]
And then we'll get the message, which is error message. Now if it's not an instance of a odd error, we just wanna call next and passing the error. Cause we want this to go to the error handler. The reason we're not doing that here is because we're just going to handle the error here.

[00:07:24]
The reason we're not doing that here is because we're just going to handle the error here. We could. Theoretically just get rid of this if statement here and just call it next E and be done, and then in our error handler or somewhere else on the app, we would just have to do this, we just have to do this. And maybe that's a strategy you wanna do as well, you don't have to handle it here, but we are handling it here, so we don't need to call.

[00:07:41]
And maybe that's a strategy you wanna do as well, you don't have to handle it here, but we are handling it here, so we don't need to call. Next, We don't need to call, like I'm not gonna do like do this and then say next, and then do like a E and then do like a next year. I'm not gonna do that. I'm just gonna handle it here because I think it's specific to this use case, so I'm gonna, I'm gonna put it here.

[00:07:53]
I'm just gonna handle it here because I think it's specific to this use case, so I'm gonna, I'm gonna put it here. But you could handle it globally if you want it, but yeah, we're gonna stop it here, so. Any questions on this? Let me show you how we're gonna use it before we make the next one, so.

[00:08:16]
Let me show you how we're gonna use it before we make the next one, so. The way this would work. Is we can pretty much go to, let's just let's just pick one, cause we're gonna we're gonna add these when we do the Controllers, but let's go to let's go to a habit route, right? So if I go to a habit route.

[00:08:34]
So if I go to a habit route. And I say import. Validate body from there, right, so I'll say I'm gonna validate the body and I'm gonna import. Z From Zod, right?

[00:08:54]
Z From Zod, right? First thing is let's identify where we want to. Make an input. So here a post.

[00:09:09]
So here a post. So I know in this post there's gonna be a a payload. I want to validate that payload is correct, so before we haven't done the database stuff yet, so we don't actually know what needs to be on a payload to be able to create a habit, but let's just assume we know right now I would make a schema. I would say cons you know, create.

[00:09:23]
I would say cons you know, create. Habit schema. Eals Z. Object.

[00:09:37]
Object. And I would just put, you know, the fields that I want to check on here again we haven't done the database stuff so I'm just gonna make up one. Let's say a habit must have a name and its type is Z. String, right?

[00:09:54]
String, right? So that's the only thing I'm gonna put on there. That's it. Super simple.

[00:10:09]
Super simple. Basic, right? So then, because I want to validate that the payload for this is correct, what I can do, so I can add a middleware. Right, in between the route and the handler.

[00:10:27]
Right, in between the route and the handler. Right here, I got it right here. So far we've only done the global middlewares, but you can add them just right here on the route. Long as long as they come before the handler, you're good.

[00:10:38]
Long as long as they come before the handler, you're good. So in this case I'll say validate body. It's a higher order function, so I call it, and it takes in a schema as an argument, so I will pass in that. Create habit schema like that.

[00:10:56]
Create habit schema like that. There we go. So now this is saying. OK, before we can run this code.

[00:11:10]
OK, before we can run this code. Which is safely assuming. That if this code is running that the payload they got sent up must be good otherwise this code would not be running so that way I don't have to do any if checks inside of this code anymore. I don't do any if checks in here.

[00:11:28]
I don't do any if checks in here. Which is what I hate doing. Because this middleware would not would not call next if the validation failed So therefore, this would never happen. That makes sense?

[00:11:49]
That makes sense? And that's an input validation, right? And I can add as many Middleware as I want here. It doesn't matter.

[00:12:04]
It doesn't matter. They are gonna call next to each other. And if I had a bunch, I could just put them all in an array like not that, I could just put them all in an array. Like this And this works too.

[00:12:21]
Like this And this works too. You can, you can add an array of Middleware here if you wanted to. The reason this is helpful is because you can define. Different groups of middleware in a separate file as a raise and then bring them over to different routes without having to redefine them again.

[00:12:33]
Different groups of middleware in a separate file as a raise and then bring them over to different routes without having to redefine them again. So it's like making your middleware portable if you have a bunch of them. Like, oh, I have this collection of Middleware that does this, does this, does this, and that's one variation. I have another variation, another variation.

[00:12:53]
I have another variation, another variation. OK, save those in arrays and then pass those arrays around as middleware inside of your house. So this is how this is how you would use it, so let's give it a try, so, Let's do a post request to slash API slash habit. And let's not put a name on it and I don't know, let's see.

[00:13:03]
And let's not put a name on it and I don't know, let's see. Let's see if something See if it does what we think it's gonna do, which I should throw a Zod error, so slash API slash. Have it, we're gonna do a post request. Is that right?

[00:13:03]
Is that right? Oops. Yep, OK, I'm not going to. Where is it at?

[00:13:03]
Where is it at? There we go. Body Raw. JSON, OK, I'm not gonna do anything.

[00:13:03]
JSON, OK, I'm not gonna do anything. I'm not gonna pass up any object. Let's see what happens if I just do that. Cannot post.

[00:13:03]
Cannot post. Is it plural again habits? Oh yeah, why do I do that? It's a bad habit.

[00:13:03]
It's a bad habit. Cool, so we got a validation failed. That's exactly what I thought was gonna happen. This is a Zod error validation field details filled so our logic is messed up on determining the fields, so we could probably fix that, but, as you can see, input invalid expected object received on oh no, actually the logic is right, there is no field.

[00:13:03]
This is a Zod error validation field details filled so our logic is messed up on determining the fields, so we could probably fix that, but, as you can see, input invalid expected object received on oh no, actually the logic is right, there is no field. It's saying you never even gave us an object. So actually that's right. So let's give it an object and let's see what we, what it says now.

[00:13:03]
So let's give it an object and let's see what we, what it says now. Cool. So now that I gave it an object, it's like, OK, congrats, you gave me an object, but I still expected name to be a string, instead it's undefined. That's extremely helpful.

[00:13:03]
That's extremely helpful. And we get that For free, just by making a schema and passing it into that Middleware, so. Pretty dope in my opinion. I would never make an API without an input validator, I would, I would never do it.

[00:13:03]
I would never make an API without an input validator, I would, I would never do it. It's like making a database without a schema. She wouldn't do it.

[00:13:03]
I don't care what Mongo tells you need a schema.

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