How to validate db.collections.insertOne inputs on mongoose and node.js - javascript

I have a problem. I am new to node.js and mongoDB (using mongoose). In MySQL when I have defined a table with required fields the database will refuse to accept input that don't conform to the model's rules. I have noticed that in mongoDB, at least, the way I have set it up, this is not the case.
I have defined the following model in blog-schema.js:
const mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = mongoose.Schema({
title: {
type:String,
required: true,
},
author: {
type: String,
required: true,
},
category: {
type: String,
required: true,
},
text: {
type: String,
required: true,
},
date: {
type: Date,
default: Date.now,
},
})
module.exports = mongoose.model('BlogPost', userSchema, 'blog');
In this, I set required:true for all fields apart from date. I then implemented this in conn.js:
const mongoose = require('mongoose')
, BlogPost = require('./schemata/blog-schema')
, db_config = require('./config')
, uri = 'mongodb://' + db_config.user + ":" + db_config.password + "#" + db_config.host + db_config.database;
DataFunctions = function (){
mongoose.connect(uri, db_config.opts);
mongoose.Promise = global.Promise;
this.connections = {};
this.schemas = {};
this.schemas.BlogPost = BlogPost;
this.connections.db = mongoose.connection;
};
DataFunctions.prototype.insert = function(data = {}, callback = null) {
var schema = this.schemas.BlogPost;
this.connections.db.on('error', console.error.bind(console, 'connection error'));
this.connections.db.once('open', function(dataStructure = schema) {
this.items = data;
if (callback != null) {
dataStructure.collection.insertOne(this.items, callback);
mongoose.connection.close();
}
else {
dataStructure.collection.insertOne(this.items, function(err, docs) {
if (err) throw err;
});
mongoose.connection.close();
}
});
mongoose.connection.close();
}
DataFunctions.prototype.retrieve = function(params = {}, columns = '', callback = null) {
var schema = this.schemas.BlogPost;
this.connections.db.on('error', console.error.bind(console, 'connection error'));
this.connections.db.once('open', function(dataStructure = schema) {
if (callback != null) {
dataStructure.find(params, columns, callback);
}
else {
dataStructure.find(params, columns, function(err, data) {
if (err) throw err;
});
}
});
}
module.exports = DataFunctions;
However, when I execute the insert function, it accepts it without error even when fields marked required are left blank. I would really appreciate any assistance in working out how to validate the data inserted into the mongoDB collection.
I am using mongoos version 5.3.6, and mongoDB version 4.0.3
Thank you.
Edit
Thanks to everyone who replied, based on some of the comments below I changed dataStructure.collection.insertOne() to dataStructure.create(), which appears to include validation.

You need to add verification on submit as well or before submit. When the form has errors it is actually invalid so check if it is invalid before submitting..
Tbh you code seems a little verbose, complicated and confusin.. is there are reason you are doing it like this? For example you are using a mongoose schema but not actually submitting with the mongoose methods which is why none of your validations are occuring.. insertOne is not a mongoose method, and you aren't using your model to save the entry. it would be model.save(data)
also you can directly save without having to call the schema again, just declare a new variable.
const post = new BlogPost(data); post.save().then(console.log).catch(console.log);
//also mongoose.connect already returns a promise
mongoose
.connect(
dbUrl,
{ useNewUrlParser: true }
)
.then(() => console.log("Connected"))
.catch(error => console.log("Failed " + error));

I believe you are passing empty string and that's why the validators are not flagging the entries as erroneous. Try passing null for the fields and check the behavaior.

Related

Mongoose populate returns empty array or list of ObjectIds

I am practicing my express.js skills by building a relational API and am struggling to populate keys in a schema.
I am building it so I have a list of properties, and those properties have units. The units have a propertyId key.
This is currently returning an empty array, whereas if i remove the populate({}) it returns an array of ObjectIds.
I've read a number of posts and some people solved this by using .populate({path: 'path', model: Model}); but this doesn't seem to be doing the trick. I think it might be the way I am adding a propertyId to the unit but I'm not sure. Can anyone see where I am going wrong? Any help will be massively appreciated.
Here are the schemas.
Property:
const mongoose = require('mongoose');
const { Schema } = mongoose;
const PropertySchema = new Schema({
title: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
units: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'unit'
}
]
});
module.exports = Property = mongoose.model('property', PropertySchema);
Unit:
const mongoose = require('mongoose');
const { Schema } = mongoose;
const UnitSchema = new Schema({
title: {
type: String,
required: true
},
propertyId: {
type: Schema.Types.ObjectId,
ref: 'property'
}
});
module.exports = Unit = mongoose.model('unit', UnitSchema);
I am then creating the unit like this:
-- api/properties/:id/units --
router.post('/:id/units', async (req, res) => {
// Get fields from req.body
const { title } = req.body;
// Get current property
const property = await Property.findById(req.params.id);
try {
// Throw error if no property
if (!property) {
return res.status(400).json({ msg: 'Property not found' });
}
// Create new unit
const newUnit = new Unit({
title,
propertyId: req.params.id
});
// Add new unit to property's units array
property.units.unshift(newUnit);
// Save property
await property.save();
// Return successful response
return res.status(200).json(property);
} catch (error) {
console.error(error.message);
return res.status(500).send('Server error');
}
});
And trying to populate in the GET request
-- /api/properties/:id/units --
const Unit = require('../../models/Unit');
router.get('/:id/units', async (req, res) => {
const property = await Property.findOne({ _id: req.params.id }).populate({path: 'units', model: Unit});
const propertyUnits = property.units;
return res.status(200).json(propertyUnits);
});
If i remove the .populate({path: 'units', model: Unit});, I get a list of unit id's like this:
[
"5ff7256cda2f5bfc1d2b9108",
"5ff72507acf9b6fb89f0fa4e",
"5ff724e41393c7fb5a667dc8",
"5ff721f35c73daf6d0cb5eff",
"5ff721eb5c73daf6d0cb5efe",
"5ff7215332d302f5ffa67413"
]
I don't know, why you don't try it like this:
await Property.findOne({ _id: req.params.id }).populate('units')
I've been try that code above and it's working.
Note: Make sure to check your req.params.id is not null or undefined and make sure the data you find is not empty in your mongodb.
Updated: I've been try your code and it's working fine.
The issue was caused by inconsistent naming and not saving the new created unit as well as the updated property.
I double checked all my schema exports and references and noticed I was using UpperCase in some instances and LowerCase in others, and saved the newUnit as well as the updated property in the POST request and it worked.

how to update mongoose specific values without getting other values converted to null?

im trying to update values in the collection, only way i got it to work is updating whole values together, if i update one value the rest of values gets null if exclude updating the file (image) i get error.
1)upading whole values sucess
1-2))mongoose
2) when image excluded "Cannot read property 'originalname' of undefined"
3)when updating image only rest of values converted to null
model
const mongoose = require ("mongoose");
const user = require('../models/usermodel')
const Schema = mongoose.Schema;
const sellerSchema= mongoose.model('seller',new mongoose.Schema({
image:{type:String,
required:true}
password:{type:String,
required:true}
lastName:{type:String,
required:true}
name:{type:String,
required:true}
numTel:{type:Number,
required:true}
email:{type:String,
required:true}
}))
module.exports = sellerSchema;
control
const sellermodel = require('../models/sellermodel');
var fs = require('fs');
const multer = require('multer');
const upload = multer({dest:__dirname +'/uploads/images'});
module.exports = {sellerupdate: function (req, res) {
sellermodel.updateOne({_id: req.params.id},
{$set:{name:req.body.name,
lastName:req.body.lastName,
email:req.body.email,
password:req.body.password,
image:req.file.originalname,}},
function (err,data) {
if (err) {res.json({ msg: 'product not found'+err })
}
else {
res.json({ msg: 'product updated successfully'+data} )
}
})
}
}
router
const sellercontrols = require("../controllers/sellercontrols");
const router = require("express").Router();
const multer = require('multer');
const upload = multer({dest:__dirname + '/uploads/images/'});
router.put('/update/:id',upload.single('image'),sellercontrols.sellerupdate);
module.exports = router;
server
const express = require('express');
const app = express();
const bodyParser = require("body-parser");
const sellerrouter= require('./routers/sellerrouter');
const db = require('./models/db');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false }));
app.use("/seller",sellerrouter);
app.listen(8080,function(){
console.log('server is running')})
please help
//This is the answer you've been looking for, I spent an hour trying to figure it. use this first syntax if email and password is inside an object else use the shorter one
const password = "new password";
const email = "new email";
let result = collection find({})
result = result.objectName
// now update email and password in the result object like this
result.email = email;
result.password = password
//after that send it back to the database
collection.updateOne({}, {$set: {fieldName: result}})
//and that's it or use this shorter one if its not in an object
collection.findOneAndUpdate({}, {$set: {email, password}})
if the email and password is inside an object use this
This is easier said being done as you are using $set and with that if you don't pass a value, it will default to null and in turn making your values in a document null. While you want to update multiple fields (could be 1 or 2 or whole document), you need to have a fail-save system so that for not changed values you keep the original ones.
This is how I would approach to this issue
1) Find the Document I want to update(sellermodel.FindOne)
2) Compare the Object Returned with passed Object, Updating the ones changed and keeping original if not changed (Hence solving null issues)
3) Update the Object with new values using (sellermodel.updateOne)
I'm assuming this would work but I didn't tested it, so you might have to change little bit here and there
sellermodel.FindOne({_id: req.params.id},(err,item)=>{
if(err) console.log(err);
else{
/* This will create a object with req.body and req.file (Assuming both are in JSON)*/
let mergedObject= {...req.body,...req.file}
/* This will Compare Object from Mongoose and the object you passed and will update the values you passed
and keeping the orignal that were not changed*/
let ObjectToUpdate=updateObjectValue(item,mergedObject)
/* Delete ObjectID otherwise it will throw an error */
delete ObjectToUpdate._id
delete ObjectToUpdate.__v
/* After You have a perfect Object You can update the values */
sellermodel.updateOne({_id: req.params.id},ObjectToUpdate,(err,data)=>{
if(err) {res.json({msg:'error occured'})}
else{
res.json({ msg: 'product updated successfully'+data})
}
})
}
})
var updateObjectValue = (obj1, obj2) => {
var destination = Object.assign({}, obj2);
Object.keys(obj1).forEach(k => {
if(k in destination) {
destination[k] = obj1[k];
}
});
return destination;
}
In your query, you are fetching all the values from req.body and passing it to $set, so I think when you don't send a value for a particular field it defaults to null and hence all values are updated to null.
If you don't want other values to change to null, you must fetch the previous values from the collection, populate in your form, so it will be accessible in req.bodywhen you submit. In postman you must pass the existing values along with the ones you want to update OR don't pass the value to $set if it's null.
If you don't want to do either of the 2, then another way would be :
sellermodel.findById({
_id: req.params.id
}, function (err, docs) {
if(err){ do something }
else{
let name = req.body.name,
lastName = req.body.lastName,
email = req.body.email,
password = req.body.password;
if(name == null || name == ''){
name = docs.name;
}
if(lastName == null || lastName == ''){
lastName = docs.lastName;
}
if(email == null || email == ''){
email = docs.email ;
}
if(password == null || password == ''){
password = docs.password;
}
//then your options will look like
let options;
if(req.file != undefined){
options = {
name: name,
lastName: lastName,
email: email,
password: password,
image: req.file.originalname
}
} else {
//Not passing the image here will not change the existing value in your document.
options = {
name: name,
lastName: lastName,
email: email,
password: password
}
}
//your update query
sellermodel.updateOne({_id: req.params.id},{$set:options},
function (err,data) {
if (err) {
res.json({ msg: 'product not found'+err })
} else {
res.json({ msg: 'product updated successfully'+data} )
}
})
}
})
In case of req.file.originalname, it will obviously throw the error you showed in one of your screenshots as req.file is not defined because you are not passing anything. I suggest you define your options to update the way. I have shown above.

Calling methods from controllers to mongoose schema file (Nodejs)

Hey guys i am pretty new to Nodejs so let me first describe my problem
I created a mongooseschema of comments like
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const commentsschema = new Schema({
firstname: {
type: String,
required: true
},
middlename:{
type:String
},
lastname:{
type:String,
required:true
},
comments:{
type:String,
required:true
},
upvote:{
type:Number
},
downvote:{
type:Number
}
});
module.exports = mongoose.model("comments", commentsschema);
then in my controllers file i created it and added it to db whenever user submits a comment
exports.postcomment = (req, res, next) => {
//All firstname, lastname etc are taken from req.body just to make my code short i havent included those lines
const commentinpage = new Comments({
firstname: fname,
middlename:mname,
lastname:lname,
comments: comment,
upvote: 0,
downvote: 0
});
return commentinpage.save().then(() => {
res.redirect("/");
});
};
now in later point of time when another user clicks on upvote button i want to increase the upvote entry in my db so i want to call a method in mongoose schema ..So i tried like this
const Comments = require("../modals/Comments");
Comments.upvoteco().then(result=>{
console.log(this.upvote)
}
and then in my schema
commentsschema.methods.upvoteco=function(){
console.log(this.upvote)
return ++this.upvote
}
but i get the error TypeError: Comments.upvoteco is not a function
You can not call the method defined in schema with the model, You can call it with the object instance i.e using and mongoose object instance(document) in that specific collection.
And to call one with the model you should define a static method:
try changing:
commentsschema.methods.upvoteco = function() {
console.log(this.upvote);
return ++this.upvote;
}
to this:
commentsschema.statics.upvoteco = function() {
console.log(this.upvote);
return ++this.upvote;
}
and try calling your method like:
Comments.upvoteco(function(err, result) {
if (err) {
console.log('error: ', err);
} else {
console.log(this.upvote);
}
});
Check the official docs for more clearity: https://mongoosejs.com/docs/2.7.x/docs/methods-statics.html
hope this helps :)

Node.js mongoose .findOne is not a function

In my app, I do return promise with my mongoose model:
var roomModel = require('../../../models/room').roomModel;
roomModel.findOne({ name: req.body.roomName })
.then(
(room) => {
return new Promise(function(resolve, reject) {
//if no room present, create one, if present, check password
if (room) {
if (room.password === req.body.roomPassword) {
return resolve(room);
} else {
return reject({
code: 401,
message: 'Room password not correct'
});
}
} else {
// create new room with given data
var newRoom = roomModel({});
newRoom.name = req.body.roomName;
newRoom.password = req.body.roomPassword;
//newRoom.users = [];
newRoom.users[0] = {
name: req.body.userName
};
newRoom.save()
.then((data) => {
console.log(data);
if (!data) {
return reject({
code: 500,
message: 'Error when saving room'
});
} else {
return resolve(newRoom);
}
});
}
});
}
)
.then((room) => {
room.findOne({ 'users.name': req.body.userName })
.then((user) => {
console.log(user);
});
})
room.js model:
'use strict';
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var userSchema = require('./user').userSchema;
var room = new Schema({
name: String,
password: String,
users: [userSchema]
});
module.exports.roomSchema = room;
module.exports.roomModel = mongoose.model('room', room);
users.js model:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var user = new Schema({
name: String
});
module.exports.userSchema = user;
module.exports.userModel = mongoose.model('user', user);
but when I try to call .findOne() function on this returned model, I get following error:
TypeError: room.findOne is not a function
is model passed in the promise not a model in next .then() statement?
Found problem by myself: I was passing not the model, on which I can use find operations, but document, on which I can perform save options (not find, since it's not a model).
Well, as the docs say "queries are not promises".
There's even a findOne() example in there...
Change your code to
roomModel.findOne({ name: req.body.roomName }).exec().then(/* your stuff */)
and you may have more luck.
You missed out exec() method in your query, try to use and get resolved.
roomModel.find({ name: req.body.roomName }).exec().then(/* your stuff */)
I should think what you're doing is dangerous. Calling a queries then() multiple times might
lead to multiple query calls.
https://mongoosejs.com/docs/queries.html#queries-are-not-promises
Also, there is no need to do exec().then(). Just calling then() executes the query; a better way to use exec() is to actually pass a callback to it.
in my case it happened that no matter how I exported it it wasn't working, and I found this solution:
var {roomModel} = require('../../../models/room')
Put the import within {}
and export it normally, i do like this:
var room = mongoose.model('room',roomSchema);
module.exports.room = room;

TypeError: populate(...).exec is not a function

I largely believe this error is due to the object I'm calling not containing the .populate function, although I have no idea how to change this to work.
To start with, here is the error in full.
TypeError: exam[0].modules[u].topics[i].populate(...).exec is not a function
at /home/ubuntu/workspace/tests/app.js:425:84
at Query.Model.$wrapCallback (/home/ubuntu/workspace/tests/node_modules/mongoose/lib/model.js:3336:16)
at /home/ubuntu/workspace/tests/node_modules/mongoose/node_modules/kareem/index.js:259:21
at /home/ubuntu/workspace/tests/node_modules/mongoose/node_modules/kareem/index.js:127:16
at nextTickCallbackWith0Args (node.js:420:9)
at process._tickCallback (node.js:349:13)
Process exited with code: 1
The specific line I'm referring to is exam[0].modules[u].topics[i].populate("questions").exec(function(err,quests) another line I believe is significantly important here is the line examBoard.find({name:req.body.examBoardName},function(err,exam) which returns exam which does not contain the .populate function.
I presume this is largely down to my lack of experience, and not a logical error, but I'm not sure.
Here is the section of code which contains the error.
app.post("/test",function(req,res)
{
console.log("\n\n\n\n")
var time = req.body.time;
var topicName = [req.body.topic1,req.body.topic2,req.body.topic3,req.body.topic4,req.body.topic5];
var topicsArray = [];
examBoard.find({name:req.body.examBoardName},function(err,exam)
{
if(err)
{
console.log(err);
}
else
{
for(var u=0;u<exam[0].modules.length;u++)
{
console.log("exam[0].modules[u]:\n"+exam[0].modules[u]);
console.log("req.body.moduleName:\n"+req.body.moduleName);
if(exam[0].modules[u].name==req.body.moduleName)
{
console.log("topicName[]:\n"+topicName[0]+"\n"+topicName[1]+"\n"+topicName[2]+"\n"+topicName[3]+"\n"+topicName[4]);
for(var i=0;i<exam[0].modules[u].topics.length;i++)
{
console.log("exam[0].modules[u].topics[i].name:\n"+exam[0].modules[u].topics[i].name);
for(var t=0;t<topicName.length;t++)
{
if(exam[0].modules[u].topics[i].name==topicName[t])
{
// exam[0].modules[u].topics[i].find({name:topicName[t]}).populate("questions").exec(function(err,quests)
exam[0].modules[u].topics[i].populate("questions").exec(function(err,quests)
{
if(err)
{
console.log(err);
}
else
{
console.log("exam[0].modules[u].topics[i].questions:\n"+exam[0].modules[u].topics[i].questions);
topicsArray.push({
name:topicName[i],
questions:quests
});
}
});
}
}
}
break;
}
}
}
});
});
Here is the examBoard schema.
var mongoose = require("mongoose");
var topicSchema = new mongoose.Schema({
name: String,
questions:[
{
type:mongoose.Schema.Types.ObjectId,
ref:"question"
}
],
});
var moduleSchema = new mongoose.Schema({
name: String,
topics: [topicSchema]
});
var examBoardSchema = new mongoose.Schema({
name: String,
modules: [moduleSchema]
});
module.exports = mongoose.model("examBoard", examBoardSchema);
And here just in case there may be something wrong here, is the importing of the schema.
var express = require("express"),
mongoose = require("mongoose"),
passport = require("passport"),
bodyParser = require("body-parser"),
LocalStrategy = require("passport-local"),
passportLocalMongoose = require("passport-local-mongoose"),
seedDB = require("./seeds"),
question = require("./models/question"),
examBoard = require("./models/examBoard"),
user = require("./models/user");
You are invoking populate method from exam[0].modules[u].topics[i] but actually the model object that hold this method is exam[0] so you can populate questions in your exam in a deep object-hierarchy like this:
exam[0].populate("modules.topics.questions")
But, wait a sec, now the model will populate the questions in all topics in all modules within this exam.
In your case, you filter by moduleName first, so you can configure populate options, so to be like this:
var options = {
path: 'modules.topics.questions',
match: { 'modules.name': req.body.moduleName }
};
exam[0].populate(options)
Lear more about populate parameters from docs.

Categories

Resources