Schema validation is a key concept while developing any backend application. Sometimes clients provide inputs which are not according to our requirements. For such cases we need to implement the validation of any information being inserted into the database.
MongoDB provides the convenience of validating data while inserting a new entry into your database documents, such as:
Checking if an email is unique or not
Checking if a given value is in a particular range
Checking if the phone number is 10 digits or not.
MongoDB also has the functionality to implement custom error messages in case a field doesn’t match the required criteria.
In this article we will go through the steps of designing a basic NodeJs application, setting up MongoDB Atlas (cloud version of MongoDB) within the application, and then we will design one simple user model and implement simple validation methods on that model.
This article is written assuming that you know how to create a cluster on MongoDB Atlas and get a connection URI from there.
Let’s set up our project. If you do not have node already installed on your system, see How to Install NodeJs. After installation, create a project folder.
Now open the terminal (Git bash is the recommended tool for windows users) in that location and run:
npm init -y
This will initialize the NPM in your current folder, and you will see a package.json file which will look like the following. The name will depend on your project folder’s name.
1 2 3 4 5 6 7 8 9
{ "name": "schema-validation", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" } }
Now we need to install some node packages. For this run the following command.
npm i express dotenv mongoose --save
Now we are ready to start our project.
Create a file called app.js in your project’s root folder and write the following code in that file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
const express = require('express'),
app = express();
//Following lines make sure that our app can parse the json data in our api calls
app.use(express.json());
app.use(express.urlencoded({
extended: false
}));
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log("Server running on port 8080");
});
In the above code, we are importing express module of node to create a server and we are setting up the server at port 8080. Here we have passed process.env.PORT to make sure if we host this application it will run on the port provided by the hosting environment.
You can run this application by typing node app.js in the terminal, but to make ours work, we will install NPM package nodemon to make development easier, as it will rerun the application automatically when we make any change. To install nodemon run:
1
sudo npm i nodemon--save - dev--global
Now run nodemon app.js in terminal. If you see an aq response like below then you are good to go.
Visit MongoDB Atlas. You will notice a dropdown with the name Project0 in the top left corner. Click on that dropdown and click on create new project.
The given window will appear after that.
Enter the name of your project, click on next, and then on create project.
Now a window will appear as shown below. Click build database and select the free tier on the next page.
After that you will get a window like this. Click on create cluster (this will take some time).
Now your cluster is created and it’s time to acquire URI.
Click on connect in the given window.
Now in the given window enter allow from anywhere in the IP column and enter username and password (save this password somewhere as you will need it to connect to the
database).
Create user and then click on choose connection method.
Click on connect your application and copy the URI that you get in your window.
Now use the acquired Mongo URI as shown below in your app.js file.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const express = require("express"),
app = express(),
mongoose = require("mongoose");
require("dotenv")
.config();
const mongouri = “mongodb + srv: //User1:"+ process.env.MONGO_PASS + "@cluster0.wakey.mongodb.net/myFirstDatabase?retryWrites=true&w=majority";
try {
mongoose.connect(mongouri, {
useUnifiedTopology: true,
useNewUrlParser: true
});
} catch (error) {
handleError(error);
}
process.on('unhandledRejection', error => {
console.log('unhandledRejection', error.message);
});
//Following lines make sure that our app can parse the json data in our api calls
app.use(express.json());
app.use(express.urlencoded({
extended: false
}));
//setting port for api
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log("Server running on port 8080");
});
Here we are using the dotenv package to set up environment variables. Create a file named .env and store your passwords like this in that file.
PORT=8080
MONGO_PASS=<your mongodb database password for this cluster>
Now create folders and files in project structure like this:
Now we are going to create a user model without validation.
Open user.js file of the models folder and write the given code. I have explained it below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
var mongoose = require('mongoose'), Schema = mongoose.Schema; /** * User Schema */ var userSchema = new Schema({ fullName: { type: String }, email: { type: String }, phone: { type: Number }, created: { type: Date, default: Date.now } }); module.exports = mongoose.model('User', userSchema);
Here we have defined user schema using mongoose (node module to manage MongoDB database) and we are exporting it to be used in other modules of nodejs application using module.exports.
Now we will create a single API post route to accept data to be inserted into the database.
Open user.js in routes files and write down the given code.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
var express = require("express"),
router = express.Router(),
User = require("../models/user");
router.post("/add", function (req, res) {
const user = new User({
fullName: req.body.fullName,
email: req.body.email,
phone: req.body.phone,
});
user.save((err, user) => {
if (err) {
res.status(500)
.send({
message: err
});
return;
} else res.status(200)
.send({
message: "User Inserted to database!!"
})
});
});
module.exports = router;
Here I am using the routers module of express to create a post route which will receive post requests with json data. Those we can access from req.body and we create one user object then save it to the database by using user.save. If we get any error, the if statement will output the element in the terminal, otherwise it will respond to the API request with status code 200 and message “User Inserted to database”.
Now we need to import this route into app.js and use it there, so make the changes to app.js mentioned in red.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
const express = require("express"),
app = express(),
mongoose = require("mongoose");
const userRoute = require("./routes/user");
require("dotenv")
.config();
const mongouri = 'mongodb+srv://User1:' + process.env.MONGO_PASS + '@cluster0.wakey.mongodb.net/myFirstDatabase?retryWrites=true&w=majority';
try {
mongoose.connect(mongouri, {
useUnifiedTopology: true,
useNewUrlParser: true
});
} catch (error) {
handleError(error);
}
process.on('unhandledRejection', error => {
console.log('unhandledRejection', error.message);
});
//Following lines make sure that our app can parse the json data in our api calls
app.use(express.json());
app.use(express.urlencoded({
extended: false
}));
//using user route
app.use(userRoute);
//setting port for api
const PORT = process.env.PORT || 8080;
app.listen(PORT, () => {
console.log("Server running on port 8080");
});
Let’s fire Postman to test this. You can install it from here.
Insert values as given (make sure you have selected request type to POST in dropdown given with the URl) and send.
You will get the following response:
This shows that our API is working fine, but clearly we need something more. We do not want to store “This is not email” as someone’s email or “123” as someone’s phone number. So let’s fix that by implementing validation for these fields in schema.
So change your schema as given:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
var mongoose = require('mongoose'),
Schema = mongoose.Schema;
/**
* User Schema
*/
var userSchema = new Schema({
fullName: {
type: String,
required: [true, "fullname not provided. Cannot create user without fullname "],
},
email: {
type: String,
unique: [true, "email already exists in database!"],
lowercase: true,
trim: true,
required: [true, "email field is not provided. Cannot create user without email "],
validate: {
validator: function (v) {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(v);
},
message: '{VALUE} is not a valid email!'
}
},
phone: {
type: String,
trim: true,
validate: {
validator: function (v) {
return /^[0-9]{10}/.test(v);
},
message: '{VALUE} is not a valid 10 digit number!'
}
},
created: {
type: Date,
default: Date.now
}
});
module.exports = mongoose.model('User', userSchema);
Here is the explanation of the above code:
fullName field - We have added a required field in fullname which checks if a username is provided or not, otherwise it will return an error message defined with it.
Email - We have added a required field, as in the case of fullname, which will work similarly. We have also added a unique field which checks if the given email is already in the database. If it already exists in the database it will give an error with an error message defined with it.
We have lowercase which converts input to lowercase and trims it.
The last field is a validator which checks a given email string against a regex expression to verify if it is in the format of an email or not. In case of an error it reports the error with an error message, otherwise the process goes to the next field.
Phone - We have added a validator field which checks if a given number is of 10 digits or not. In case of error it does the same process as above, otherwise data gets stored in the database.
So let’s use Postman to create the same request we did earlier, and we will get error messages.
We get the response as shown below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
{
"message": {
"errors": {
"email": {
"name": "ValidatorError",
"message": "\"this is not email\" is not a valid email!",
"properties": {
"message": "\"this is not email\" is not a valid email!",
"type": "user defined",
"path": "email",
"value": "\"this is not email\""
},
"kind": "user defined",
"path": "email",
"value": "\"this is not email\""
},
"phone": {
"name": "ValidatorError",
"message": "123 is not a valid 10 digit number!",
"properties": {
"message": "123 is not a valid 10 digit number!",
"type": "user defined",
"path": "phone",
"value": 123
},
"kind": "user defined",
"path": "phone",
"value": 123
}
},
"_message": "User validation failed",
"name": "ValidationError",
"message": "User validation failed: email: \"this is not email\" is not a valid email!, phone: 123 is not a valid 10 digit number!"
}
}
Our validation is working fine.
Now let’s test this with valid data.
Our validation is working fine.