Blank document and unhandled promise rejection when making an add subdocument request - node.js

I am working on my first request that would add a subdocument to a document in MongoDB and I'm struggling with it. My database is a collection of users, and each user has an array of words they working on learning to translate in the application I am building. I am currently having two issues
when I make a request in postman to add a new word to my user's array of words, I add a new object that has ID, but none of the other property value pairs that I have in the word sub-model, and in the request(greek, english, success, timestamp).
my command prompt gives me the following errors
(node:8320) UnhandledPromiseRejectionWarning: Unhandled promise
rejection. This error originated either by throwing inside of an async
function without a catch block, or by rejecting a promise which was
not handled with .catch(). (rejection id: 3) (node:8320)
UnhandledPromiseRejectionWarning: ValidationError: user validation
failed: words.0.greek: Path greek is required., words.0.english:
Path english is required., words.0.success: Path success is
required., words.0.timeStamp: Path timeStamp is required.
The second error is confusing because in my mind it should be word.greek and words.english to get the value from the each word object. However, it adds a 0 between the object and its property/value pair.
My mongoose model for the subdocument is as follows
const wordSchema = new Schema({
greek: {
type: String,
required: true,
trim: true,
minlength: 1,
index: { unique: true, sparse: true },
},
english: {
type: String,
required: true,
minlength: 1
},
success: {
type: Number,
required: true
},
timeStamp: {
type: Date,
required: true
},
});
This is my request to add the word to the User's array of words.
router.post("/update/:id",(req, res) =>{
console.log(req.body.greek)
var greek = req.body.greek;
var english = req.body.english;
var success = req.body.success;
var timeStamp = req.body.timeStamp
var newWord = {
greek: greek,
english: english,
success: success,
timeStamp: timeStamp
}
User.findById(req.params.id)
.then((user) => {
user.words.push(newWord);
user.save()
res.status(200).json(user)
.catch((err) => {res.status(400).json(err)})
})
.catch((err) => {res.status(400).json("Error: "+err)})
});
Any help would be greatly appreciated! I've done some googling on adding subdocuments to a document, but I still haven't found the solution.

Instead of fetching and then updating the document you can directly update the document in one DB call.
router.post("/update/:id",(req, res) =>{
console.log(req.body.greek)
var greek = req.body.greek;
var english = req.body.english;
var success = req.body.success;
var timeStamp = req.body.timeStamp
var newWord = {
greek: greek,
english: english,
success: success,
timeStamp: timeStamp
}
User.findOneAndUpdate({ _id: req.params.id }, {
$push: {
words: newWord
}
})
.then((user) => {
if(!user){
return res.status(404).json({
message: 'Not User matches given ID'
});
}
res.status(200).json(user);
})
.catch((err) => {res.status(400).json("Error: "+err)})
});

one thing I see is user.save() will return a promise, which you do not handle, hence the document will not be save. maybe consider:
User.findById(req.params.id)
.then(async (user) => {
user.words.push(newWord);
await user.save()
res.status(200).json(user)
.catch((err) => {res.status(400).json(err)})
})
.catch((err) => {res.status(400).json("Error: "+err)})

Related

Unable to update MongoDB collection by matching _id

First, I Registered a user using His email and password.
Now if
I try to Update or Make user details by matching the Id assigned by the Mongo Database while registering the user.
it's not accepting it.
error is like this
Parameter "filter" to find() must be an object, got 60b10821af9b63424cf427e8
if I parse it like
Model.find(parseInt(req.params.id))
it shows a different error.
Well here's the Post Request
//Post request to create user details by matching Id.
// Id I am trying to match is the id that was given by the database
app.post("/user/:id", async(req, res) => {
console.log("not found");
if (await Model.find(req.params.id)) {
const users = new Model({
fName: req.body.fName,
sName: req.body.sName,
birth: req.body.birth,
phone: req.body.phone,
SSN: req.body.SSN
});
const result = await users.save();
console.log(result);
return res.send({
"Success": true,
});
} else {
console.log(req.params.id);
res.status(404).send({ "message": false });
}
});
Here's the schema
const LoginSchema = new mongoose.Schema({
email: { type: String, required: true },
password: { type: String, required: false },
otp: String,
token: String,
fName: String,
sName: String,
birth: Number,
phone: Number,
SSN: Number
});
Here are the headers I used
const express = require("express");
const app = express();
const mongoose = require("mongoose");
app.use(express.json());
app.use(express.urlencoded({ extend: true }));
Database id assigned which I use
{
_id: 60b1131271129a3cf8275160,
email: 'Pak#gmail.com',
password: '$2b$10$FWAHu4lP9vn14zS/tWPHUuQJlO7mjAUTlPj.FliFAZmCNA23JA3Ky',
__v: 0
}
The error:
Parameter "filter" to find() must be an object, got 60b10821af9b63424cf427e8
means: You need to provide an object as the argument of the find() method, not a string ("60b10821af9b63424cf427e8" in this case). Moreover, find() will give you an array, if you find an item in the database, use findOne() instead.
Change from :
await Model.find(req.params.id)
to :
await Model.findOne({_id : req.params.id})
Another way is to use findById() method like this : await Model.findById(req.params.id)
Likely the error is caused by the fact that you're passing a string from the request when Mongoose is expecting an instance of mongoose.Types.ObjectId. You should be able to fix the problem by casting the string into said type.
await Model.find(mongoose.Types.ObjectId(req.params.id))

Mongoose saves invalid data without throwing validation errors if model.validate() is called first

MongoDB 4.2.2 and Mongoose 5.8.3 (latest) and NodeJS 13.3.0 (Windows x64)
If I create a schema and model, then create an instance of the model and add some data, then run validate(), then save(): even if validate() fails, the data is saved into the collection, without throwing an additional validation error.
Is this a bug, or am I doing something wrong?
Here's the test code:
var mongoose = require('mongoose')
mongoose.connect("mongodb://user:pass#localhost/mydb")
db = mongoose.connection
var Schema = mongoose.Schema
var PartSchema = new Schema({
name: {
type: String,
required: true,
validate: {
validator: (v) => v !== 'asdf' // Don't allow name to be 'asdf'
}
},
number: {
type: String,
required: true,
validate: {
validator: (v) => !v.includes(' ') // Don't allow spaces in part number.
}
}
})
var ProductSchema = new Schema({
name: String,
parts: [PartSchema]
})
var Part = mongoose.model('Part', PartSchema)
var Product = mongoose.model('Product', ProductSchema)
var p1 = new Product({name:"Baseball Bat", parts:[ new Part({name:"First part", number: "003344"}), new Part({name: "Second part", number: "554422"}) ]})
p1.parts.push(new Part({name: "No number, so invalid"})) // this one is invalid because no part number is specified (required)
p1.parts.push(new Part({name: 'asdf', number: 'zzzzzaaaa'}))
p1.parts.push(new Part({name: 'bbbb', number: 'with a space'})) // This one is invalid because number has spaces.
p1.validate()
.then(() => {console.log('Validation successful')})
.catch((err) => { console.log("Validation failed.")})
p1.save()
.then(()=>{ console.log("Saved successfully")})
.catch((err)=>{console.log("Save ERROR", err)})
Running this code yields the following:
Validation failed.
Saved successfully
And the new document appears in the database:
However, if I remove the p1.validate() before calling save(), the save function's catch() block triggers and the item is not saved:
Save ERROR Error [ValidationError]: Product validation failed: parts.2.number: Path `number` is required., parts.3.name: Validator failed for path `name` with value `asdf`, parts.4.number: Validator failed for path `number` with value `with a space`
at ValidationError.inspect
... snipped
May be you need to use p1.save() inside the promise chain.
p1.validate()
.then(res => {
console.log("Validation successful");
})
.then(() => {
return p1.save();
})
.then(res => {
console.log("saved success ", res);
})
.catch(err => {
console.log("Some error.", err);
});

mongoose-unique-validator detects all inputs as already existed

I am trying to create a unique email value in MongoDB. I used mongoose-unique-validator to implement that but resulted in error claiming that my unique emails that I just inputted are already existed.
This is the error message I received from trying to input unique email.
"message": "User validation failed: email: Error, expected email to be
unique., _id: Error, expected _id to be unique."
They said the Email and _id are not unique. But.., _id is an auto-generated value to be unique in MongoDB meaning that it should not be detected as a duplicated value.
I am not sure if that was caused by my own implementation so I would look forward to see any assumption or ways to debug to the root cause of this too. I tried restarting from fresh Mongo DB and manually inputting uniquely via Postman but still no hope.
These are a part of the codes that might be related to the data creation on MongoDB
UserModel.js
var uniqueValidator = require('mongoose-unique-validator');
const { Schema } = mongoose;
const UsersSchema = new Schema({
email: { type: String, unique: true, required: true},
hashedPassword: String,
salt: String,
});
UsersSchema.plugin(uniqueValidator, { message: 'Error, expected {PATH} to be unique.' });
UsersSchema.set('autoIndex', false);
Server.js ---- /api/users/register
const finalUser = new Users(user);
finalUser.setPassword(user.password);
finalUser.save((err, data) => {
if (err) {
return res.status(400).json(err)
}
return res.json({ user: finalUser.toAuthJSON() })
})
Additional Information
I tried this solution from Sritam to detect another email with the same value but it still claims that the inputted email is already existed.
UsersSchema.path('email').validate(async (value) => {
const emailCount = await mongoose.models.User.countDocuments({email: value });
return !emailCount;
}, 'Email already exists');
"message": "User validation failed: email: Email already exists"
You can use validateModifiedOnly option in Document.save(). _id and email fields will never be validated unless they are modified. The code should look like this:
finalUser.save({ validateModifiedOnly: true }, (err, data) => {
if (err) {
return res.status(400).json(err)
}
return res.json({ user: finalUser.toAuthJSON() })
})
Found the issue and solution!
They are acting weird like in the question because the model was not initialized.
I must perform Schema.init() before performing any model validation.
The solution is to add UsersSchema.init().then(() => {...your operation})
Now Server.js ---- /api/users/register should look like this.
Users.init().then(() => { // where Users is my UsersModel
finalUser.save((err, data) => {
if (err) {
return res.status(400).json(err)
}
return res.json({ user: finalUser.toAuthJSON() })
})
})
Hope this helps other developers who experience similarly odd error!

Mongoose 'find' is returning an empty array when queried with conditional params

I have a collection of some tweets and I need to fetch the tweets specific to a particular user. I'm using mongoose 'Find' query to find the tweets. I'm taking the user_id from req.params and using it as conditional parameter but mongodb is returning an empty array.
I have tried debugging with console.log and user_id seems to be correct. Type of 'user_id' is String which is expected and i don't seem to find the reason as why an empty array is returned even though there are matching entries in my collection.
Moreover, when i use other conditional parameters(apart from user), entries are fetched with no problem.
app.get('/search/:id',(req,res) => {
var _id = req.params.id;
if(!users.includes(_id)){
res.status(400)
res.send('Sorry... User not present in the list')
}
else{
Tweet.find({user: _id}).then((tweets)=>{
res.status(200).send(tweets)
}).catch((e) =>{
res.status(500).send();
})
}
})
My mongoose Schema:
const Tweet = mongoose.model('Tweet',{
user:{
type: String,
trim:true
},
text:{
type: String,
required: true,
trim:true
},
verified:{
type: Boolean
}
})
"I have tried debugging with console.log and user_id seems to be correct. Type of 'user_id' is String which is expected and i don't seem to find the reason as why an empty array is returned even though there are matching entries in my collection. "
accoutring to your this statement you search for "user_id" is document but you can not define user_id in your mongoose schema
const Tweet = mongoose.model('Tweet',{
user:{
type: String,
trim:true
},
text:{
type: String,
required: true,
trim:true
},
verified:{
type: Boolean
}
})
use user instead of id
app.get('/search/:user',(req,res) => {
var user = req.params.user;
if(!users.includes(user)){
res.status(400)
res.send('Sorry... User not present in the list')
}
else{
Tweet.find({user: user}).then((tweets)=>{
res.status(200).send(tweets)
}).catch((e) =>{
res.status(500).send();
})
}
})

Catch error when using populate with mongoose

I have the next model and route with mongoose:
In my colection I have some invalids id's to "cidade" field and this is why I am getting the error showing below.
The error happens in the line:
.populate('cidade')
Is there a way to execute my router(code is below) in:
router.get('/:id',function(req,res,next){ .....
without stop on that error?
If an invalid "id" is found, I´d just like to ignore it and proceed to next.
My collections are too big and can have some invalids "ids" to "cidade" field.
//error
angular.js:14328 Possibly unhandled rejection: {"data":{"message":"Cast to ObjectId failed for value \"Ararendá\" at path \"_id\" for model \"Cidade\"","name":"CastError","stringValue":"\"Ararendá\"","kind":"ObjectId","value":"Ararendá","path":"_id"},"status":500,"config":
//models and route
//cidade
cidadesSchema = new mongoose.Schema({
uf: {type: String, unique:true},
cidade: {type: String, unique:true}
});
module.exports = mongoose.model('Cidade', cidadesSchema,'cidades' );
//profiss
var profissionaisSchema = new mongoose.Schema({
nome: {type: String, unique:true},
cidade: {type:mongoose.Schema.Types.ObjectId, ref:'Cidade'},
estado: {type:mongoose.Schema.Types.ObjectId, ref:'Estado'},
cep: {type: String},
});
module.exports = mongoose.model('Profissional', profissionaisSchema,'profissionais' );
//route
const callback=function(err,data,res){
if (err) return res.status(500).json(err);
return res.status(200).send(data);
}
router.get('/:id',function(req,res,next){
const query=req.params.id;
Profissional.findById(query).populate('profissao')
.populate('cidade')
.exec( (err,data) => {
callback(err,data,res)
});
});
I don't think you can tell Mongoose to just ignore those errors and keep going, so you're going to have to implement the population yourself (which should be relatively easy because you're using findById which would only yield, at most, one document).
Here's some (untested) code:
Profissional.findById(query).populate('profissao').exec( (err, profi) => {
if (err) {
return res.status(500).json(err);
} else if (! profi || ! /^[a-f0-9]{24}$/i.test(profi.cidade)) {
return res.status(200).send(profi);
}
Cidade.findById(profi.cidade).exec((err, cidade) => {
if (err) {
return res.status(500).json(err);
}
profi.cidade = cidade;
return res.status(200).send(profi);
});
});
If the cidade property looks like a valid ObjectId, it will run a query to retrieve it, otherwise it won't bother.

Resources