How do you implement MongoDB's Schema Validation feature in Express server? I'm working on a simple todo app and decided to use the native MongoClient instead of mongoose but i still do want to have a schema.
Base on MongoDB's docs here: https://docs.mongodb.com/manual/core/schema-validation/#schema-validation You can either create a collection with Schema or update an existing collection without schema to have one. The commands are run in the mongo shell, but how do you implement it in express?
So far what i did is make a function that returns the Schema Validation commands and call it on every routes but i get an error saying db.runCommand is not a function.
Here's my express server:
const express = require("express");
const MongoClient = require("mongodb").MongoClient;
const ObjectID = require("mongodb").ObjectID;
const dotenv = require('dotenv').config();
const todoRoutes = express.Router();
const cors = require("cors");
const path = require("path");
const port = process.env.PORT || 4000;
const dbName = process.env.DB_NAME;
let db;
const app = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
MongoClient.connect(process.env.MONGO_URI,{useNewUrlParser: true},(err,client)=>{
if(err){
throw err;
console.log(`Unable to connect to the databse: ${err}`);
} else {
db = client.db(dbName);
console.log('Connected to the database');
}
});
/* Schema Validation Function */
const runDbSchemaValidation = function(){
return db.runCommand( {
collMod: "todos",
validator: { $jsonSchema: {
bsonType: "object",
required: [ "description", "responsible","priority", "completed" ],
properties: {
description: {
bsonType: "string",
description: "must be a string and is required"
},
responsibility: {
bsonType: "string",
description: "must be a string and is required"
},
priority: {
bsonType: "string",
description: "must be a string and is required"
},
completed: {
bsonType: "bool",
description: "must be a either true or false and is required"
}
}
} },
validationLevel: "strict"
} );
}
/* Get list of Todos */
todoRoutes.route('/').get((req,res)=>{
runDbSchemaValidation();
db.collection("todos").find({}).toArray((err,docs)=>{
if(err)
console.log(err);
else {
console.log(docs);
res.json(docs);
}
});
});
/* Get Single Todo */
todoRoutes.route('/:id').get((req,res)=>{
let todoID = req.params.id;
runDbSchemaValidation();
db.collection("todos").findOne({_id: ObjectID(todoID)}, (err,docs)=>{
if(err)
console.log(err);
else {
console.log(docs);
res.json(docs);
}
});
});
/* Create Todo */
todoRoutes.route('/create').post((req,res,next)=>{
const userInput = req.body;
runDbSchemaValidation();
db.collection("todos").insertOne({description:userInput.description,responsible:userInput.responsible,priority:userInput.priority,completed:false},(err,docs)=>{
if(err)
console.log(err);
else{
res.json(docs);
}
});
});
/* Edit todo */
todoRoutes.route('/edit/:id').get((req,res,next)=>{
let todoID = req.params.id;
runDbSchemaValidation();
db.collection("todos").findOne({_id: ObjectID(todoID)},(err,docs)=>{
if(err)
console.log(err);
else {
console.log(docs);
res.json(docs);
}
});
});
todoRoutes.route('/edit/:id').put((req,res,next)=>{
const todoID = req.params.id;
const userInput = req.body;
runDbSchemaValidation();
db.collection("todos").updateOne({_id: ObjectID(todoID)},{ $set:{ description: userInput.description, responsible: userInput.responsible, priority: userInput.priority, completed: userInput.completed }},{returnNewDocument:true},(err,docs)=>{
if(err)
console.log(err);
else
res.json(docs);
console.log(db.getPrimaryKey(todoID));
});
});
/* Delete todo */
todoRoutes.route('/:id').delete((req,res,next)=>{
const todoID = req.params.id;
runDbSchemaValidation();
db.collection("todos").deleteOne({_id: ObjectID(todoID)},(err,docs)=>{
if(err)
console.log(err)
else{
res.json(docs);
}
});
});
app.use('/todos',todoRoutes);
app.listen(port,()=>{
console.log(`Server listening to port ${port}`);
});
I also tried it on the initial client connection but i got the same error:
You validation schema will be added to the MongoDB driver when you run it for the first time. So you don't need to perform validation every time you run a query. You will get a validation response directly from the driver's validation schema that was added initially. Again you will not explicitly get to know which object is causing the error. The validation response will be more generic.
Related
I am trying to create product using nodejs and mongodb through my api for ecommerce management inventory but getting error as validation, the following below are my code that I have used.
This is my mongoose.js code
// require the library
const mongoose = require('mongoose');
require('dotenv').config();
// connecting database
mongoose.connect(process.env.MONGO_CONNECT);
// aquire the connection
const db = mongoose.connection;
//on error
db.on('error', console.error.bind(console, 'error connecting to db'));
// on success
db.once('open', function(){
console.log('Successfully connected to the database');
})
// exporting db
module.exports = db;
this is my schema
const mongoose = require('mongoose');
// Schema for Product [fields: id, name, quantity] with timeStamps
const productSchema = new mongoose.Schema({
name:{
type: String,
required: true
} ,
quantity:{
type: Number,
required: true
}
},{timestamps:true})
const Product = mongoose.model('Product', productSchema);
// exporting schema
module.exports = Product;
this is my controllerproduct js file
const Product = require('../models/product');
// Fetch List of products
module.exports.list = async function(req, res){
try{
let product = await Product.find()
return res.status(200).json({
data:{
message: "Products",
products: product
}
})
}catch(err){
console.log('Error in fetching List of products',err);
return res.send('Error in fetching List of products'+ err);
}
}
// Create Product
module.exports.create = async function(req, res){
try{
let product = new Product({
name: req.body.name,
quantity: req.body.quantity,
});
await product.save()
return res.status(200).json({
data:{
message: "Product created",
products: product
}
})
}catch(err){
console.log('Error in creating product',err);
return res.send('Error in creating product'+ err);
}
}
// Delete Product
module.exports.delete = async function(req, res){
try{
let product= await Product.findById(req.params.id);
await product.remove();
return res.status(200).json({
data:{
message: "Product removed from list",
}
})
}catch(err){
console.log('Error in removing product',err);
return res.send('Error in removing product'+ err);
}
}
// Update quantity
module.exports.update = async function(req, res){
try{
let product= await Product.findById(req.params.id);
// Here using req.query to update quantity [NOTICE: using number for query]
let num = parseInt(req.query.number);
if(product.quantity + num < 0){
return res.status(200).json({
data:{
message: "Try Again! Product quantity could not be less then 0",
}
})
}else{
product.quantity = product.quantity + num
await product.save()
return res.status(200).json({
data:{
message: "Product Quantity updated successfully",
products: product
}
})
}
}catch(err){
console.log('Error in updating quantity of the product',err);
return res.send('Error in updating quantity of the product:::'+ err);
}
}
module.exports.home = async function(req, res){
try {
return await res.send('Hello Admin')
} catch (error) {
console.log('Error at home',err);
return res.send('Error at home page:'+ err);
}
}
this is main index.js file
const express = require('express');
const port = process.env.PORT||8800;
const app = express();
const db = require('./config/mongoose');
// To parse incoming requests
app.use(express.urlencoded());
// It parses incoming requests with JSON
app.use(express.json())
// making connection to index of route
app.use('/', require('./routes/index'))
// connecting to port
app.listen(port, function(err){
if(err){
console.log(`Error! connecting Port : ${err}`);
}
console.log(`Connected on Port : ${port}`);
})
I'm trying to create product through my Api using postman but I am getting this error, any help will be appreciated:
Aditya Kumar#DESKTOP-980JOV2 MINGW64 ~/nodews/ecommerce-Api
$ node index.js
body-parser deprecated undefined extended: provide extended option index.js:7:17
Connected on Port : 8800
Successfully connected to the database
Error in creating product Error: Product validation failed: name: Path `name` is required., quantity: Path `quantity` is required.
at ValidationError.inspect (C:\Users\Aditya Kumar\nodews\ecommerce-Api\node_modules\mongoose\lib\error\validation.js:49:26)
at formatValue (node:internal/util/inspect:782:19)
at inspect (node:internal/util/inspect:347:10)
at formatWithOptionsInternal (node:internal/util/inspect:2167:40)
at formatWithOptions (node:internal/util/inspect:2029:10)
at console.value (node:internal/console/constructor:324:14)
at console.log (node:internal/console/constructor:360:61)
at module.exports.create (C:\Users\Aditya Kumar\nodews\ecommerce-Api\controllers\product_controller.js:34:17)
at processTicksAndRejections (node:internal/process/task_queues:96:5) {
errors: {
name: ValidatorError: Path `name` is required.
at validate (C:\Users\Aditya Kumar\nodews\ecommerce-Api\node_modules\mongoose\lib\schematype.js:1346:13)
at SchemaString.SchemaType.doValidate (C:\Users\Aditya Kumar\nodews\ecommerce-Api\node_modules\mongoose\lib\schematype.js:1330:7)
at C:\Users\Aditya Kumar\nodews\ecommerce-Api\node_modules\mongoose\lib\document.js:2835:18
at processTicksAndRejections (node:internal/process/task_queues:78:11) {
properties: [Object],
kind: 'required',
path: 'name',
value: undefined,
reason: undefined,
[Symbol(mongoose:validatorError)]: true
},
quantity: ValidatorError: Path `quantity` is required.
at validate (C:\Users\Aditya Kumar\nodews\ecommerce-Api\node_modules\mongoose\lib\schematype.js:1346:13)
at SchemaNumber.SchemaType.doValidate (C:\Users\Aditya Kumar\nodews\ecommerce-Api\node_modules\mongoose\lib\schematype.js:1330:7)
at C:\Users\Aditya Kumar\nodews\ecommerce-Api\node_modules\mongoose\lib\document.js:2835:18
at processTicksAndRejections (node:internal/process/task_queues:78:11) {
properties: [Object],
kind: 'required',
path: 'quantity',
value: undefined,
reason: undefined,
[Symbol(mongoose:validatorError)]: true
}
},
_message: 'Product validation failed'
}
It might be due to a miss config of an HTTP request header, please make sure you are sending a valid JSON payload when calling the API by adding a application/json content-type header
Examples:
CURL
curl -X POST http://localhost:8000 -d '{"name": "Dummy", "quantity": 1}' -H "Content-Type: application/json"
Fetch
const res = await fetch('http://localhost:8000', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
req.body.name is most likely coming up as undefined . try logging it out to the console, if it is indeed undefined, then it may be due to some system error. you can try restarting your server or even your local machine.
the problem may also be from the request you are sending in postman. make sure that all the parameters are in the right place
This is my index.js. When I hit my API using postman, there is a bug in my getdata() method code that causes it to return the else statement rather than finding the requested resource. I'm not sure why the data targeting by ID can't be found. When I use console.log to test my code, the try statement is entered but did not find anything from the database and catch statement is returned.
const connectToMongo = require('./db');
var cors = require('cors');
const express = require('express');
const bodyParser = require('body-parser');
const photo = require('./models/photoModel');
const formidable = require('formidable');
const fs = require('fs');
const photoModel = require('./models/photoModel');
connectToMongo();
const app = express();
app.use(cors());
const port = 5000;
//app.use(express.json());
app.use(bodyParser.json());
const userData = (req, res) => {
const form = new formidable.IncomingForm();
console.log('1');
form.parse(req, (err, fields, file) => {
console.log('2');
if (fields) {
console.log('3');
const { email, mno, name } = fields;
if (!email || !mno || !name) {
console.log('4');
return res.status(400).json({
error: 'Fill all the fields',
});
}
}
if (file.photo) {
if (file.photo.size > 4000000) {
return res.status(400).json({
error: 'image size is too long',
});
}
const user = new photo(fields);
user.photo.data = fs.readFileSync(file.photo.filepath);
user.photo.contentType = file.photo.type;
user.save((err, user) => {
if (err) {
return res.status(400).json({
error: 'Not save in db',
});
}
console.log('above json');
res.json(user);
});
}
});
};
// The issue is here
const getdata = async (req, res) => {
try {
console.log('yes');
const photo = await photo.find({ photo: req.photo.id });
res.json(photo);
} catch (error) {
return res.status(400).json({
error: 'not find',
});
}
};
//router
app.post('/userdashboard', userData);
app.get('/getdata', getdata);
app.listen(port, () => {
console.log(`Dashboard Backend listening on port ${port}`);
});
This is my modelschema
const mongoose = require('mongoose');
const { Schema } = mongoose;
const PhotoSchema = new Schema({
name:{
type:String,
trim:true,
required:true
},
email:{
type:String,
trim:true,
required:true
},
photo:{
data:Buffer,
contentType:String
},
mno:{
type:Number,
required:true
}
});
module.exports=mongoose.model('photo',PhotoSchema);
First mistake you did was not adding console.error(error), if you did it would have shown exact error message.
In this case req.photo is undefined and you are trying to access id property of undefined which causes error to be thrown.
const getdata = async (req, res) => {
try {
const { photo } = req.body
// TODO: use custom error class for validation errors instead of default Error class
if (!photo) throw new Error('photo is missing')
if (!photo.id) throw new Error('photo.id is missing')
// TODO: validate photo.id is valid monogoid
const photos = await photo.find({ photo: photo.id })
res.json(photos)
} catch (error) {
console.error(error) // <-- this was missing
res.status(400).json({ error: 'not find' }) // provide good error message
return
}
}
This is my database connection:
app.js
const express = require("express");
const app = express();
var { MongoClient } = require("mongodb");
MongoClient.connect("mongodb://localhost:27017", (err, client) => {
if (err) return console.log(err);
db = client.db("MyDb");
app.listen(5000, () => {
console.log("listening on 5000");
});
});
And this is my insert function:
router.post(
"/register",
[
check("email")
.notEmpty()
.withMessage("Email Field is empty"),
check("email")
.isEmail()
.withMessage("Your email is not valid")
],
function(req, res) {
const errors = validationResult(req);
if (errors.length >= 0) {
res.render("register", { errors: errors.errors });
console.log(errors.errors);
return;
}
const { name, email, password } = req.body;
const newUser = new User({
name: name,
email: email,
password: password
});
newUser.save(function(err) {
if (err) throw err;
console.log(true);
});
}
);
And this is my user model:
User.js
const mongoose = require("mongoose");
const UserSchema = mongoose.Schema({
name: { type: String, require: true },
email: { type: String, require: true, unique: true },
password: { type: String, require: true },
created_at: Date,
updated_at: Date
});
const User = mongoose.model("User", UserSchema);
module.exports = User;
There is no error in terminal or browser. When I click the "register" button, the app will freeze and there is no error message at all.
I already tested many tips concerning the database connection but couldn't solve the issue.
I find there are two order of problems in the proposed code, at least as we can read it in your question:
First, I can't find any binding between mongoose and the established mongodb connection
Second, your route handler does not seem to return any status code / content to the
caller
So, for as I see it, you can
change connection setup as follows
mongoose.connect('mongodb://localhost/test', {useNewUrlParser: true})
.then((conn, err) => {
app.listen(5000, () => {
console.log("listening on 5000");
});
});
in order to bind mongoose with MongoDb configuration
retust a status code, e.g. 201, when the new User has been saved:
newUser.save(function(err) {
console.log('Result', err)
if (err) throw err;
console.log(true);
res.send(201)
});
This way I prevent the application hanging up on receiving request...
I hope this can help you!
validationResult() "Extracts the validation errors from a request and makes them available in a Result object." https://express-validator.github.io/docs/validation-result-api.html Therfore, if you don't have any errors this object will contain no errors ( you can check with .isEmpty()), your endpoint doesn't send a response, and leaves the requestor waiting.
Im trying to make an API with node JS (express and jwt) with a mongodb.
I've make some routes (like to get markers for example), that's working fine.
BUT i've also make a route in order to post some markers. When I query it (that takes long time, and with Postman), and I've an error like :
"Could not get any response"
and in my console :
Error: socket hang up
My route controller :
const model = require('../models/markers');
module.exports = {
create: function(req, res, next) {
model.create({
param_1: req.body.param_1,
param_2: req.body.param_2,
param_3: req.body.param_3,
}, function (err, result) {
if (err)
next(err);
else
res.json({status: "success", message: "Marker added successfully", data: null});
});
},
getAll: function(req, res, next) {
let list = [];
model.find({}, function(err, items){
if (err){
next(err);
} else{
for (let item of items) {
list.push({
id: item._id,
param_1: item.param_1,
param_2 : item.param_2,
param_3: item.param_3
});
}
res.json({status:"success", message: "Markers list found", data:{markers: list}});
}
});
},
};
And for my route :
const express = require('express');
const router = express.Router();
const markerController = require('../controllers/markers');
router.get('/', markerController.getAll);
router.post('/', markerController.create);
module.exports = router;
My model :
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const MarkerSchema = new Schema({
param_1: {
type: String,
required: true
},
param_2: {
type: String,
},
param_3: {
type: [{
type: String,
enum: ['0.5', '1', ',1.25', '1.5', '2', '3+']
}],
default: ['pending']
}
});
module.exports = mongoose.model('Marker', MarkerSchema);
My get router works fine but my post route don't.
You should check #Will Alexander comment, if it's a copy paste you have a typo in:
param_2: req.bodyparam_2,
I am trying to build a Node API which uses MongoDB to create and fetch information from a file which contains Book information in the following JSON format:
{
Name: String,
Author: String,
Price: Number
}
I am unable to add info to the DB. I get the Book printed successfully message though. But when I see the DB, a JSON document is created only with an ID and the version key _v.
Below is my API code:
var express = require('express');
var app = express();
var bodyParser=require('body-parser');
var mongoose=require('mongoose');
var book = require('./automobile/reading/book')
//configuring the app for the bosy data intake for post operations
app.use(bodyParser.urlencoded({extended:true}));
app.use(bodyParser.json());
var PORT= process.env.PORT || 3000;
mongoose.connect('mongodb://localhost:27017/helloworldapi');
var router = express.Router();
app.use('/api', router);
router.use(function(req,res,next){
console.log('There is something happening in the API');
next();
})
router.route('/books')
.post(function(req,res){
var bookDB = new book();
bookDB.name=req.body.name;
bookDB.author=req.body.author;
bookDB.price=req.body.price;
bookDB.save(function(err){
if(err){
res.send(err);
}
res.json({message: 'Book was successfully printed'});
});
})
.get(function(req,res){
book.find(function(err, books){
if(err){
res.send(err);
}
res.json(books);
});
});
router.route('/books/:book_id')
.get(function(req,res){
book.findById(req.params.book_id, function(err, books){
if(err){
res.send(err);
}
res.json(books)
});
});
router.route('/books/name/:name')
.get(function(req,res){
book.find({name:req.params.name}, function(err, books){
if(err){
res.send(err);
}
res.json(books);
});
});
router.route('/books/author/:author')
.get(function(req,res){
book.find({author:req.params.author}, function(err, books){
if(err){
res.send(err);
}
res.json(books);
});
});
app.listen(PORT);
console.log('server listening on port '+PORT);
While trying to perform a GET Operation after performing the POST Operation, I am getting the below response:
[
{
"_id": "5a788cf1ad829e3aa4b91287",
"__v": 0
}
]
Below is my shema:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var BookSchema = new Schema({
Name: String,
Author: String,
Price: Number
});
module.exports= mongoose.model('book',BookSchema);
Please don't mind the root folder automobiles. Planned to create something else in the beginning.
You can use
router.route('/books')
.post(function(req,res){
var bookDB = new book(req.body);
bookDB.save(function(err){
if(err){
res.send(err);
}
res.json({message: 'Book was successfully printed'});
});
})
But the req.body values should be same on schema values.
I believe it is because you are not assigning to the right properties. In your route, you assign to the lowercase properties. However, you Schema defines uppercase properties. See if matching the case works:
router.route('/books')
.post(function(req,res){
var bookDB = new book();
bookDB.name=req.body.name; // This needs to match the property in the schema exactly
bookDB.author=req.body.author;
bookDB.price=req.body.price;
bookDB.save(function(err){
if(err){
res.send(err);
}
res.json({message: 'Book was successfully printed'});
});
})
var BookSchema = new Schema({
name: String, // Now I'm lower case
author: String,
price: Number
});
Or you can make your router uppercase:
router.route('/books')
.post(function(req,res){
var bookDB = new book();
bookDB.Name=req.body.name;
bookDB.Author=req.body.author;
bookDB.Price=req.body.price;
bookDB.save(function(err){
if(err){
res.send(err);
}
res.json({message: 'Book was successfully printed'});
});
})