Unable to update MongoDB collection by matching _id - node.js

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))

Related

Removing an element from the array in MongoDB through mongoose

I've defined a MongoDB model using mongoose in my NodeJS application as:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const userSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
rToken: [String]
})
module.exports = mongoose.model("User", userSchema);
Every rToken is unique. I want to remove an element from the rToken array specified by the user in the request body, therefore, I've created a route below:
module.exports.logout = async (req, res) => {
const token = req.body.token;
User.updateOne({}, {$pull: {$in: [token]}});
res.sendStatus(204);
}
But it isn't removing the element and I'm still getting 204 status. Please help.
If rToken is an object you can remove element like that, assuming you remove a password key:
const { password, ...rTokenWithoutPassword } = rToken;
Then use rTokenWithoutPassword to do anything.
If you want to remove more than one key, salt and password for example:
const { password, salt, ...rTokenWithoutPassword } = rToken;
If you need to remove from within mongoDB, assuming rToken is an array of strings, you need to modify your Mongoose model because actually it is a String, not an array.
Then, according to mongodb documentation: https://www.mongodb.com/docs/manual/reference/operator/update/pull/
Modify your update request, assuming "abcd" is the value to pull off:
User.updateOne({}, { $pull: { rToken: { $eq: 'abcd' } } });
Here is a playground: https://mongoplayground.net/p/Fq-bH7vBUF7

MongoDB E11000 duplicate key error collection with no unique field in schema

I'm setting up a simple blog form with title, content, and json base64 image data to pass to a local MongoDB server. This is my post route:
router.post('/:userName/posts/new', isAuthenticated, async (req, res) => {
let parsedImage = req.body.file != null && req.body.file != '' ? JSON.parse(req.body.file).data : ''
const newPost = new Post({
title: req.body.title,
content: req.body.content,
imageJson: parsedImage
})
try {
await newPost.save()
res.redirect("/account")
} catch (e) {
console.log(e)
res.redirect("/home")
}
})
And this is my schema model:
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true,
},
content: {
type: String,
required: true,
},
imageJson: {
type: String
}
});
module.exports = mongoose.model('Post', postSchema)
no unique: true as you can see. The first time I tried running this, it ran perfectly, redirecting directly to the /account page, as intended in the try catch block. But, on a second attempt, using a different image, different content, and different title, it threw this error:
MongoError: E11000 duplicate key error collection: fullstacksus.posts index: userName_1 dup key: { userName: null }
I have no idea what's going on since userName is a database name used in my user account route, but not in my post-making route. Why is it showing up again? Thanks for your answers.
In your MongoDB there is a unique index on {userName: 1}. Since your schema does not have a userName field, it is missing, i.e. null, in every document, so the index rejects it.
Use the mongo shell to connect and drop the index.

avoid duplicates on MongoDB using Mongoose

Hi I'm new to MongoDB and Moongoose I'm trying to avoid my api's users to store on the Mongo database duplicated contact's name but seems like it's not working at all.
This is how I'm trying to do it right now the name and the phone number are mandatory and also the name must be unique otherwise it should throw an error.
const contactSchema = new mongoose.Schema({
name: {
type: String,
required: true,
unique: true
},
number: {
type: Number,
required: true
}
});
app.post('/api/persons', (request, response) => {
const body = request.body;
const person = new Contact({
name: body.name,
number: +body.number
});
person.save()
.then(saved => {
response.json(saved);
})
.catch(error => {
return response.status(400).json({
error: 'content missing'
});
});
})
If I send a post request with missing name or number it already throws an error but seems like it's not gettin the unique value validation.
Finally found a package that allows me to avoid duplicted entries on Mongo. I used this package following the documentation instructions:
https://github.com/blakehaswell/mongoose-unique-validator#readme
This is the code I had to write:
const uniqueValidator = require('mongoose-unique-validator');
const contactSchema = new mongoose.Schema({
name: {
type: String,
required: true,
unique: true
},
number: {
type: Number,
required: true
}
});
contactSchema.plugin(uniqueValidator);
the error of unique validation is weird so you can use unique-validator plugin, after that when you send a post request with missing name or number, the error is about required: true
Refer to validation
Validators are not run on undefined values. The only exception is the required validator.
Since both the fields(name and number) in your DB are required.
Instead of directly passing the request body to the query, you can do something like this.
const name = body.name;
const number = body.number;
if(!name || !number) {
// Return response saying either of the fields is empty.
// It's not a good practice to hit the DB with undefined values.
}
let personDetails = {
"name": name,
"contact": contact
};
const person = new Contact(personDetails);
Regarding the unique validation either you can use the unique-validator plugin as suggested by Mohammad Yaser Ahmadi or you can make a DB call to check if the name and number are unique and then hit the save method if that's is feasible for your database.
If you want both the fields name and number to be combined unique you can create Compound Index as follows:
contactSchema.index({ name: 1, number: 1 }, { unique: true });
You can read more on Compound Indexes here: https://docs.mongodb.com/manual/core/index-compound/

Saving data to array in mongoose

Users are able to post items which other users can request. So, a user creates one item and many users can request it. So, I thought the best way would be to put an array of users into the product schema for who has requested it. And for now I just want to store that users ID and first name. Here is the schema:
const Schema = mongoose.Schema;
const productSchema = new Schema({
title: {
type: String,
required: true
},
category: {
type: String,
required: true
},
description: {
type: String,
required: true
},
userId: {
type: Schema.Types.ObjectId,
ref: 'User',
required: true
},
requests: [
{
userId: {type: Object},
firstName: {type: String}
}
],
});
module.exports = mongoose.model('Product', productSchema);
In my controller I am first finding the item and then calling save().
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findById(productId).then(product => {
product.requests.push(data);
return product
.save()
.then(() => {
res.status(200).json({ message: "success" });
})
.catch(err => {
res.status(500).json({message: 'Something went wrong'});
});
});
};
Firstly, is it okay to do it like this? I found a few posts about this but they don't find and call save, they use findByIdAndUpdate() and $push. Is it 'wrong' to do it how I have done it? This is the second way I tried it and I get the same result in the database:
exports.postRequest = (req, res, next) => {
const productId = req.body.productId;
const userId = req.body.userId;
const firstName = req.body.firstName;
const data = {userId: userId, firstName: firstName};
Product.findByIdAndUpdate(productId, {
$push: {requests: data}
})
.then(() => {
console.log('succes');
})
.catch(err => {
console.log(err);
})
};
And secondly, if you look at the screen shot is the data in the correct format and structure? I don't know why there is _id in there as well instead of just the user ID and first name.
Normally, Developers will save only the reference of other collection(users) in the collection(product). In addition, you had saved username also. Thats fine.
Both of your methods work. But, second method has been added in MongoDB exactly for your specific need. So, no harm in using second method.
There is nothing wrong doing it the way you have done it. using save after querying gives you the chance to validate some things in the data as well for one.
and you can add additional fields as well (if included in the Schema). for an example if your current json return doesn't have a field called last_name then you can add that and save the doc as well so that's a benefit..
When using findById() you don't actually have the power to make a change other than what you program it to do
One thing I noticed.. In your Schema, after you compile it using mongoose.modal()
export the compiled model so that you can use it everywhere it's required using import. like this..
const Product = module.exports = mongoose.model('Product', productSchema);

Validation on user inputs with MongoDB and mongoose?

I would an unified method to validate my schemas assuming a user input, so not only apply the built-in validation on save/update, but also on find(), etc..
var User = mongoose.model("User", new Schema({
name: {type: String, minlength: 5, maxlength: 128, required: true, unique: true});
}));
What I want is to run validators every time before I run the queries with mongoose, to assure that the user inputs comply with the global schema rules.
Something like that in my route:
var username = $.get["username"], //An input from GET querystring
User = mongoose.model("User");
User.validate({name: username}, function(err) {
if (err) return console.log("not valid input"); //i.e. too short
//run query if valid
});
Is there a plugin (assumed that I'm not using Express) or maybe other already included in mongoose for that?
Documentation: http://mongoosejs.com/docs/validation.html
It is supported in mongoose by default. If you are looking for generic validation before each save operation you can specify the field to be validated path and the validation validate(function(valueEntered, howToRespond). If the validation is not passed the error will be thrown as shown in the example below.
Example: Using bluebird for sake of convenience. The following snippet validates the email, before every save operation.
var mongoose = require('bluebird').promisifyAll(require('mongoose'));
var Schema = mongoose.Schema;
var UserSchema = new Schema({
name: String,
email: {
type: String,
lowercase: true
},
password: String,
});
UserSchema
.path('email')
.validate(function(value, respond) {
var self = this;
return this.constructor.findOneAsync({ email: value })
.then(function(user) {
if (user) {
if (self.id === user.id) {
return respond(true);
}
return respond(false);
}
return respond(true);
})
.catch(function(err) {
throw err;
});
}, 'The specified email address is already in use.');

Resources