The Express. js Course - Module 7: Mongoose Validation | Shivendra Raghuvanshi | Skillshare
Search

Playback Speed


1.0x


  • 0.5x
  • 0.75x
  • 1x (Normal)
  • 1.25x
  • 1.5x
  • 1.75x
  • 2x

The Express. js Course - Module 7: Mongoose Validation

teacher avatar Shivendra Raghuvanshi, Lead Developer and Online Teacher

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Watch this class and thousands more

Get unlimited access to every class
Taught by industry leaders & working professionals
Topics include illustration, design, photography, and more

Lessons in This Class

    • 1.

      Class Introduction

      1:59

    • 2.

      Implementing Validation in Mongoose

      7:40

    • 3.

      Utilizing Built-in Mongoose Validators

      5:02

    • 4.

      Creating Custom Validators in Mongoose

      3:16

    • 5.

      Handling Validation Errors in Mongoose

      3:24

    • 6.

      Schema Type Options and Customization in Mongoose

      5:36

    • 7.

      Restructuring our FareWheels Project

      6:14

  • --
  • Beginner level
  • Intermediate level
  • Advanced level
  • All levels

Community Generated

The level is determined by a majority opinion of students who have reviewed this class. The teacher's recommendation is shown until at least 5 student responses are collected.

2

Students

--

Project

About This Class

Module 7: Mongoose Validation takes a deep dive into one of the most crucial aspects of backend development—data validation. In this module, you'll learn how to ensure the integrity and reliability of your application's data with Mongoose. You'll explore built-in validators, create custom validation logic, and handle errors gracefully. By the end of this module, you’ll be able to implement robust validation techniques that improve your application's stability and user experience.

What You’ll Learn

  • How to implement validation in Mongoose schemas to ensure data integrity.
  • Utilizing Mongoose's built-in validators to enforce constraints.
  • Creating custom validators tailored to your application's specific needs.
  • Handling and responding to validation errors effectively.
  • Customizing schema type options to meet data requirements.

We’ll also restructure the FareWheels application to integrate these validation techniques into real-world scenarios.

Meet Your Teacher

Teacher Profile Image

Shivendra Raghuvanshi

Lead Developer and Online Teacher

Teacher
Level: All Levels

Class Ratings

Expectations Met?
    Exceeded!
  • 0%
  • Yes
  • 0%
  • Somewhat
  • 0%
  • Not really
  • 0%

Why Join Skillshare?

Take award-winning Skillshare Original Classes

Each class has short lessons, hands-on projects

Your membership supports Skillshare teachers

Learn From Anywhere

Take classes on the go with the Skillshare app. Stream or download to watch on the plane, the subway, or wherever you learn best.

Transcripts

1. Class Introduction: Welcome back to the Express JS course, Module seven, Mangus validation. This class is a continuation of the Express JS course series. My name is Shawn Raganhi and I'm excited to guide you through this module where we take your back and development skills to the next level by mastering Mangus validation. In this module, we will focus on validation, a crucial aspect of any robot backend system. You will learn how to implement validation in mangos to ensure data integrity. Then you will learn utilizing built in validators for quick and effective constraint enforcement, then creating custom validators tailored to specific application needs. After that, you will learn to handle and respond to validation errors gracefully, and finally, customize schema options for greater flexibility and control. This module is a game changer for developers aiming to create reliable and scalable applications. By mastering Mongoose validation, you will write cleaner error free code, reduce bugs, and ensure that your data is always consistent and trustworthy. These are some of the essential skills for building production ready APIs. In this module as a project, we will continue building the Fair Wheels application. First, you will enhance the company's API by replacing the in memory array with Mongo DB. Then you will create a new customer's APA, complete with full crowd operations and validation. This hands on project will solidify your understanding of integrating Mongoose validation into real world applications. This module is packed with valuable techniques and practical experience to help you build better backends. So let's dive in and make your skills shine. See you in the first lecture. 2. Implementing Validation in Mongoose: I So this is the core schema that we defined earlier in the section. Now by default, all these properties that we define here, they are optional. So if I create a course and leave out all these properties, then save the purse to the database, that will be perfectly valid operation. Mongo DV doesn't care that we have a course that doesn't have a name or doesn't have a price. Wen this lecture, I'm going to show you how to implement validation. Now for this demo, I'm only going to talk about the required validator. But we have more built in validators that you are going to learn about in the next lecture. So let's make this name required. First, we replace the string with an object here. We set the type to string, and then we set required, true. With this, if I create a course without a name, so let's come in this out. At the time I try to save this course to the database, I'm going to get an exception. Let me show you. So first, let's go back to the end of this file and delete, remove course. Now come in do, create course. Now back in terminal, let's run the application. All right. Look at what we got. We got this variation error. If you see that error, that basically means that you have not handled that rejection. So just to refresh your memory, remember, promises can be in three states. Initially, they are pending, then they can be either fulfilled or rejected. In this case, we have a rejected promise. So we have not handled that properly. So back in the code here, when saving this course to the database, look, the save method returns a promise. We are awaiting there to get the result. So with this implementation, we are only assuming the success scenario. If the promise is rejected, we don't have any code to handle that. So earlier, I told you that you should put this code in a tr cache block. So let's move this here. Add the cache block here, we get an exception, and then we can display exception message on the canso. Now, back in the terminal, let's run the application one more time. So we don't get a warning anymore. Instead, we get this error message. Forse validation fail, path name is required. So if we have an invalid course object, Mongoose doesn't allow us to save the course through the database. So validation automatically kicks in at the time we try to save a course through the database. We can also manually trigger the validation. Let me comment out these two lines. This course object has a validate method. Now this validate method, look, it returns a promise of void, so we can await it. And if our course is invalid, then we will get an exception, and we will end up in this cache block. So let's go back to the terminal node index has, look, we got the same validation error. Path name is required. Now, one thing I personally don't like about the design of mongoose is that this varidate method retains a promise of void. So here we don't have any results. Idally, this validate method should return a boolean. So we could call this is valid. And then if the course is not valid, we could have some logic here. So this is a design flaw and mongoose. It returns a promise of void. Now, the only option to get that kind of boolean is to pass a callback here. So instead of awaiting the promise, we have to go back to the callback pase approach. So here we pass a function that takes an error object, and then we can check if we have any errors. Then we can execute some logic. Now you might ask, we already have this cache block here. So if we have any validation errors, we can execute that kind of logic here. That is true, but writing code like that is a little bit messy. So I hope sometime in the future, Mongo's team change this method to Dana Boolean. Now that aside, let's move this code and go back to our original code. One thing I need to clarify here is that this validation that we implemented on the name property is only meaningful in mongoos. Mongo DB doesn't care about this name property. So if we have worked with databases like SQL server or MySQL, you know that in these databases, we can define validation at a database level. For example, in our courses table, we're going to have a name column and we can mark that column as required. With that, we cannot store a course without the name in our database. In Mongo DB, we don't have that. Mongo Dew doesn't care about any of this stuff. So this validation we have implemented here is only meaningful in mongoos. At the time we try to save a course, mongoose runs the validation logic. And if the course is not valid, it won't save it to the database. Now, one last thing I need to clarify here before we finish this lecture. Earlier in the section about Express, I introduced you to a nought package called Joy. So you might be asking when we have two kinds of validation. Should we use Joy or should we use mongoose as validation? The answer is both. These kind of validations complement each other. So we use joy in our restful APIs. We use that as the first attack to make sure the data that the client is sending us is valid data. But we still need this kind of validation in mangos to make sure that the data we save to the database is in the right shape because it is possible that the client sends us a valid course in the body of the request. But when we create a course object in our SDDP service, perhaps we may forgot to set the name property to what we get from request dot body dot name. So by enforcing validation in mangos, we can ensure that programming errors like this won't result in invalid documents to be persisted in a Mongo DV database. Next, we are going to look at the built in valid errors in mangos. 3. Utilizing Built-in Mongoose Validators: In the last lecture, we learned about this required validator, which is one of the built in validators in mangos. In this lecture, we are going to have a closer look at these built in validators. So this required property here, we can set that to a boolean or a function that returns a boolean. And this is useful when you want to conditionally make your property required or not. For example, let's imagine price is only required if the course is published. Let's add the required validator here. First, we replace number with an object. Set the type here back to number. And then set required. Here, we need to pass a function. So function. And in this function, we return a boolean. So we return this to reference this course object dot is published. So if it's published is true, then price will be required. Okay. Now here, I need to clarify something. In this particular case, we cannot replace this function with an arrow function. In other words, if we do this, our validator will not work because arrow functions don't have their own this. They use this value of the enclosing execution context. In this particular case, there is a function somewhere in mongoose that is going to call this function. This reference we have here will reference that function, not the course object we're dealing with here. So we need to revert this back to a regular function. Now, let's test this validation. So here's our course object. I'm going to remove the price, and you can see the course is published. So we should get two validation errors. Now, back in the terminal, let's run the application. Look, path price is required and path name is also required. Now later, I will show you how to get individual error messages from this exception. For now, we are just getting the message as a simple string. So this is our required validator. We can set that to a simple boolean or a function to conditionally make a property required. Now, depending on the type of properties we have here, we have additional built in validators. For example, with strings, we also have Min length and max length. Let me show you. I'm going to break this down. Here we add min length. Let's say we want to make sure at least we have five characters. Here we can also set max length. Let's say 255 characters. We also have MT, and here we can pass a regular expression. Now in this particular case, it doesn't make any sense to apply a regular expression on the name of a course. So I'm going to commend this out. Another useful validator we have for strings is Enum. I'm going to create another property here. Let's call that category. And set the type two string. Now here, we can use the Enum validator. We set this to an array of valid strings. Let's say we have a few predefined categories, web mobile network, and so on. So when creating a course, the category we set should be one of these values. Otherwise, we are going to get a validation error. So let me make this required. Now back to our course object. Let's add the category, and I'm going to set this to just attach. Now let's bring the name back as well as the price. So we can only see the validation error for the category. So back in the terminal node, indexed or chase, look, category is not a valid enum value for path category. So these are the validators specific to strings. We have Min Land, max length, match for using a regular expression, and num. For numbers, we have min and max. So here, price is a number. We can set a minimum of $10 and a maximum of $200. And we also have these two validators for dates. In the next lecture, you are going to learn about custom validators. 4. Creating Custom Validators in Mongoose: Sometimes the built in validators in mango don't give us the kind of validation we need. For example, look at this tags property. Our tag is a string array. What if we want to enforce this rule that every core should have at least one tag? We cannot use the required validator because with required, we can simply pass an empty array, and that will be perfectly valid for Mangus' point of view. So here we need a custom validator. So first, we need to replace this with an object to replace this with an object. Here we set the type to array. Now we need to define a custom validator. So here we set the valid property to an object. In this object, we have a property called validator, which we set to a function. This function takes an argument, which is short for value. And here we can implement our custom validation logic. So we can return something like this. If Galen is greater than zero, then this property will be valid. We can also set a custom message here. So this valid object has another property that is message. So message, we set that too. A core should have at least one tag. Now, let's test this. So back in our course object, first, I'm going to set category to a valid value, so web, then I'm going to pass an mt array. So back in the terminal node, indexed or has. All right, look, a core should have at least one dig. What if we exclude this property like this? Let's see what happens. One more time, we get the same message. A core should have at least one dig. So if we don't set this property because we define its type as an array, mongoose will initialize this to an empty array. Now, what if we set this to null? So now back in the terminal. Okay, look, cannot read property length of nub. This is not the kind of validation message we want to get. So we need to modify our validation logic to something like this. If we as a value and the length property is greater than zero, and this property will be valid. So back in the terminal, let's run this one more time. A core should have at least one tag, beautiful. So this is how you define a custom validator. You set the validate property to an object. In this object, you add this validator function, and optimally, you can set a message. 5. Handling Validation Errors in Mongoose: So far, we have only displayed a simple message about our validation error. In this lecture, we are going to examine this error object in more detail. So this exception that we get in the cache block has a property called errors. In this object, we have a separate property for each invalid property in our course object. Let me show you what I mean. So back in our course object, here are the properties of ports. Right now, here we have an invalid property that is tag. Let's also make category an invalid property. I'm going to set this to a dash. Now with this errors object that we get will have two properties. One is tags, the other is category. Okay? So we can iterate for all the properties in this error object and get more details about each validation error. So for field in error that errors, here we do a console log. We go to R t errors, find that property, get its value. Now, this is a validation error object. Let's have a look. So back in terminal node index dot Js, so let me scroll up and see what is happening here. All right. So look here, we have a validation error object. This is the message. Below that, we have the stack trace, okay? Now, all that aside, these are the properties we have in the validation error option. So here we have a property called properties, which gives us information about the validation requirements for this property. So here we have access to our validator function. You can see the type of this validator is Enum. These are the valid enum values for this property. A determines the name of our property, in this case, category, and value is the current value. So our validation error object has properties and a few other properties. One is kind, which is set to Enum, and this is basically a shortcut to properties type. We also have another short property path, which is set to category and value, which is the current value for this property. So here we are iterating over these validation error objects. And here we have multiple validation errors. So this is the first one. And below that, look, we have another validation error object. This is for a core should have at least one tag. So if you scroll down, you can see the kind of this validation error is user defined because here we have a custom validator. The path is tags, the current value is null. So if you want to get the validation error message for each invalid property, we can simply access this message property. Now, back in the terminal, let's run the application again. So we have two validation error messages. Dash is not a valid enum value for path category, and here is the second validation error for our text property. 6. Schema Type Options and Customization in Mongoose: So when defining a schema, you have learned that we can set the type of a property directly here or use a schema type object. Now, this object has a few properties. You have learned about some of them. You know the type property, you know the required enum, and so on. In this lecture, we are going to look at a few more useful properties that are available on these chema type objects. So for strings, we have three additional properties that you can use. We have lowercase. We can set that to true. And with this, mongoose will automatically convert the value of this category property to lowercase. Let me show you how that works. Back in our course object. Okay, first, I'm going to remove this validation error. So let's change category to web and note that here I'm using an uppercase W, right? I'm going to set tags to, let's say, nt en now back here in the terminals from the application. Okay, we created a course object and saved it to the database. Now look at the category. It's lowercase web. And if you look compass, let's refresh this list. So here's our new document. Category is set to a lowercase web. So this is how the lowercase property works. We also have uppercase. Again, we can set that to true. Now, technically, we should use one of these, not both of them. And finally, we have trim. So if we have paddings around our string, mongoose will automatically remove those paddings. So these three properties are available when using strings. Now we have a couple of more properties in the schema type object, and these properties can be used when defining any property irrespective of its type. For example, let's go back to our price property. Let's say we always want to round the value of the price, so we can define a custom getter and a custom setter. So get here we pass an arrow function that takes V or value as an argument. Now we can define our custom logic or getting this value, so we can apply math dot round round this value. Now we can similarly define a custom set. And here we pass a similar function. So we go to math dot round of. So whenever we set the price property, the set of function will be called, and here we will round that value. So with this, if we go back to our course object and set the price to 15.8, let's see what happens. So back in the terminal, let's run the application again. Look, we created a new coarse object, and the price is set to 16. So here, when we set this value our custom setter was called. And here we rounded this value. Now back in Compass, here's our last course document. You can see the price is set to 16. Now let's edit this. You can see the type of this property is set to Int 32, which is an integer. I'm going to change this to double and then change the value 16-15 0.8. And finally, click Update to commit my changes. So here, I'm simulating a scenario where we have a document that was stored in the database before we implemented this rounding logic. In this case, if you read these courses and then access the price property, our custom getter will be called. And here, we'll round that value. Let me show you how that works. Let's go back to our get courses function. Previously, we implemented this paging logic in the demo. We don't need that. So I'm going to comment out these two lines, and then I'm going to change the query object so we can read the particular course. So we want to get the course with ID set to copy the value of that course ID. So back in Compass, here's our course ID. Copy this and paste it here. Here, we're going to get one course so we can access the first element of this array. Now if you read the price property, you will see that the value of this property will be rounded. So here, let's read the price property. Let's come into this create course and call get courses. Back in terminal. Let's run the application. Okay, look, price is 16, even though in the database, we stored it as 15.8. So that's how these custom getters and setters work. The setter is called when we set the value of a property like here, and the getter is called when we read the value of a property. 7. Restructuring our FareWheels Project: All right. So here's our fair wheels application. Now, if you look at the customer's module, you can see here on the top, we are defining this customer model. And below that, we have our route handlers. Then after all these route handlers, we have this validate customer function. So this is a fairly simple application. And in this module, we have 85 lines of code. If you look at the definition of the customer object or the customer model, this is not a big complex model. In a real world application, our customer model is going to be more complex. So the code in this module is going to grow, and that's something we need to address in this lecture. To keep our applications maintainable, we should ensure that each module is responsible for only one thing. That is the single responsibility principle in practice. In this application, this customer's module that we have is part of the routes folder. So technically, all we should have in this module is a definition of our customer's route. The definition of a customer object doesn't really belong in this module. So in this lecture, we're going to extract this code and put it somewhere else. So I've created this models folder. In this folder, we are going to have modules like customer Dogs, company dogs, and so on. So let's add a new filer, customer dot js. Now back in customers dot JS, I'm going to move the definition of the customer model inside our new model. So let's move that here. Now here, we have a dependency to mangos. So let's go back here on the top. This is the line for loading mangos. We also need to load I, as you will see in a second. Now back in our customers module, I'm also going to move the function for validating a customer inside our new module. So and paste it here at the end. Now we have the single responsibility principle in practice. Our customer module has all the code for defining and validating a customer object. It knows what a customer should look like. Our customers JS module knows all about various routes to work with customers. So here we don't have any code other than handling express routes. Okay? That means we no longer need to load joy in this module because validating a customer object is now the responsibility of this new module, customer dot JS. Okay? Now, finally, at the end of this module, we need to export this customer class as well as this validate customer function. So we write module dot exports. We add customer here in this object or a shorter way is to simply use the export property. Earlier, I told you that exports is reference to module dot exports. So we can simply add extra properties in that object. Similarly, we need to export this validate function. We can make the name shorter. So instead of validate customer, we can use validate and we set this to validate customer. Okay? Now back in our old module, here we have two choices. One is to load the customer module like this. So constant customer, we set this to require. Now here, we need to go one level up and then go to the models folder and then load the customer module. So this customer module, this object has two properties. One is customer, the other is validate. If we load this customer module like this, in order to reference the customer type or the customer model, we have to write customer module dot customer. And you can see this looks really ugly. So a better approach is to use object destructuring. This object that is returned from loading this module, you know that it has two properties, customer and validate. We can destructure that object and load it into these two constants, customer and validate. So we put the curly braces here when defining these constants. And with this, the customer constant will be set to what is returned from this module. Mar. Okay. So we don't have to repeat customer in several places. Similarly, this validate property will be set to what is returned from this module, validate. Now finally, we need to replace this validate customer with palidate which is a shorter name and cleaner here's another reference that we need to update. So validate. Now with this change, if you look at the number of lines of code we have in this module, look, we have 54 lines. So initially, we had over 80 lines of code, and now we have about 50 lines of code. As an exercise, I want you to modify the company's module. So in routes, here we have companies. Similarly, here on the top, we have the definition of the company model. I want you to extract this code and put it in a separate module.