avoid duplicates on MongoDB using Mongoose - node.js

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/

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 model.create() is not allowing duplicate inputs without any key in model being `'unique:'true'`

I am using mongoose along with nodejs to insert data. I am using MVC and in my controller I have this code:
module.exports.create_tracsaction = function (req, res) {
Company.findOne({ username: req.body.req_from }, function (err, user) {
if (user.vaccine.extra < req.body.req_vac) {
return res.redirect('/vaccine');
}
else {
var data = {
req_from: req.body.req_from,
req_to: req.body.req_to,
amt_req: req.body.req_vac,
transaction_status: "Awaiting confirmation",
vaccine: user.vaccine.name,
transaction_value: user.vaccine.price * req.body.req_vac
}
Transaction.create(data);
return res.redirect('/vaccine');
}
})
console.log(req.body);
};
This is my schema
const transactionSchema = new mongoose.Schema({
req_from: {
type: String,
required: true
},
req_to: {
type: String,
required: true
},
amt_req: {
type: Number,
required:true
},
transaction_status: {
type: String,
default: "Awaiting confirmation"
},
vaccine: String,
transaction_value: Number
}, {
timestamps: true
});
Even though non of the fields have property unique:'true', I am getting this error:
MongoError: E11000 duplicate key error collection: innovate_dev.transactions index: username_1 dup key: { username: null }
How to remove this error? The first time I sent data from views there was no error but from thereafter it's giving this error every time.
Let me know if you need anything else. Thanks in advance :D
It looks like a unique index got created at some point on the username field of transactions. That according your schema, it no longer exists. Thus every time you make an entry null is being indexed and throwing an error.
If you are using Compass or another GUI you can go in and delete it.
Otherwise to remove with MongoShell:
use innovate_dev
It will switch to your db
db.transactions.dropIndex('username_1')

TypeError: expected string but received array postman

I was trying to send form data that has multiple fields with the same name, I'm getting back "TypeError: expected string but received array".
I think the problem is with postman, I want to have multiple participant fields, and those would be added to the should be added to the array.
final results of array
// this is from models/Battle
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Create Schema
const BattleSchema = new Schema({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
date: {
type: Date,
default: Date.now
},
category: {
type: Number,
required: true // this will come from the selected category
},
winner: {
type: Number,
default: 0
},
status: {
type: Number,
default: 0 // 0 means the battle is closed, 1 means the battle is open for votes, the status will stay 0 until all participants dropped
},
participants: [
{
participant: {
type: Schema.Types.ObjectId,
required: true
}
}
]
});
module.exports = Battle = mongoose.model('battles', BattleSchema);
//this is from routes/api/battles
// #route POST api/battles
// #desc Create battle
// #access Private
router.post(
'/create-battle',
passport.authenticate('jwt', { session: false }),
(req, res) => {
const { errors, isValid } = validateBattleInput(req.body);
// Check Validation
if (!isValid) {
// If any errors, send 400 with errors object
return res.status(400).json(errors);
console.log(errors);
}
const newBattle = new Battle({
user: req.user.id,
category: req.body.category,
participant: req.body.participant
});
//save
newBattle.save().then(battle => {
// const participant = req.body.participant;
const participant = req.body.participant;
// add participants to array
battle.participants.push( participant );
console.log(typeof req.body.participant);
// get the inserted id
const battleId = battle._id;
res.json(battle);
});
}
);
// this is battle validation
const Validator = require('validator');
const isEmpty = require('./is-empty');
var bodyParser = require('body-parser');
module.exports = function validateBattleInput(data) {
let errors = {};
data.category = !isEmpty(data.category) ? data.category : '';
data.participant = !isEmpty(data.participant) ? data.participant : '';
if (Validator.isEmpty(data.category)) {
errors.category = 'Category field is required';
}
// if (Validator.isEmpty(data.challenger)) {
// errors.challenger = 'Challenger field is required';
// }
if (Validator.isEmpty(data.participant)) {
errors.participant = 'Participant field is required';
}
return {
errors,
isValid: isEmpty(errors)
};
};
TypeError: Expected string but received Array. ---throws an error in postman as well as in a terminal window. I suspect it could be the user schema definition mismatch
Please check your user model user schema eg
name: {
type: String,
required: true
}
it's receiving something else than expected.
try in your "body" tab, selecting "raw", and then to the right, select "JSON (application/json)" instead of "text".
I'm assuming your API endpoint uses JSON instead of a url-encoded form data, just because you are running an API using express and mongoose. but you should clarify that on the question if it isn't the case.
Write a proper JSON body, I mean, use double quotes for keys as in:
{"model": { "property": "value", "property2": 1}}
and try with the wrapping object {"model": <YOUR BODY HERE>} or without to see what works for you, as it's typical to wrap the object, but sometimes people don't use them. (seeing this in your code: req.body.participant makes me think you probably don't).
(PS: not related with the question, but personally prefer ARC or Insomnia for rest clients, as the interface for them is cleaner)
If you want data to be sent in participants array all the fields should be participants and not participant
try sending data through raw data and then selecting application/data for better formatting
When testing in postman - Just figured out Key value must match your validation function defined variables. It's better to be consistent across your development.

Number type in mongoose schema always get saved as string

I have this simple schema, with contact as Number data type. Strangely, the input data(contact) always gets saved as string in mongodb. I have no idea, as to what is happening in the background, or am I missing something.
var mongoose = require('mongoose');
var memberSchema = new mongoose.Schema({
.
. Other fields are not shown/ necessary
.
contact: {
type: Number,
required: true,
}
});
module.exports = mongoose.model('Member',memberSchema);
In my routes file, I send user data for adding into DB like this
exports.post = function(req,res,next){
if(!req.body.name || !req.body.email || !req.body.contact)
return res.render('index', {error: 'Fill Name, Email and Contact'});
//req.body.contact = parseInt(req.body.contact);
var member = {
name: req.body.name,
email: req.body.email,
contact: req.body.contact
}
As you can see req.body.contact is whatever user has entered in a form and I pass it that way.
The problem is, either I am not understanding the actual concept or there must me something more to it.
Note: I am not using any express or mongoose validator middlewares.
Any help would be highly appreciated.
You could test the contact value against a regular expression. The following example shows a custom validator using a regular expression to validates against a 10 digit number. Custom validation is declared by passing a validation function:
var memberSchema = new Schema({
contact: {
type: Number,
validate: {
validator: function(v) {
return /d{10}/.test(v);
},
message: '{VALUE} is not a valid 10 digit number!'
}
}
});
var Member = mongoose.model('Member', memberSchema);
var m = new Member();
m.contact = '123456789';
// Prints "ValidationError: 123456789 is not a valid 10 digit number!"
console.log(m.validateSync().toString());
m.contact = 0123456789;
// Prints undefined - validation succeeded!
console.log(m.validateSync());

Skip or Disable validation for mongoose model save() call

I'm looking to create a new Document that is saved to the MongoDB regardless of if it is valid. I just want to temporarily skip mongoose validation upon the model save call.
In my case of CSV import, some required fields are not included in the CSV file, especially the reference fields to the other document. Then, the mongoose validation required check is not passed for the following example:
var product = mongoose.model("Product", Schema({
name: {
type: String,
required: true
},
price: {
type: Number,
required: true,
default: 0
},
supplier: {
type: Schema.Types.ObjectId,
ref: "Supplier",
required: true,
default: {}
}
}));
var data = {
name: 'Test',
price: 99
}; // this may be array of documents either
product(data).save(function(err) {
if (err) throw err;
});
Is it possible to let Mongoose know to not execute validation in the save() call?
[Edit]
I alternatively tried Model.create(), but it invokes the validation process too.
This is supported since v4.4.2:
doc.save({ validateBeforeSave: false });
Though there may be a way to disable validation that I am not aware of one of your options is to use methods that do not use middleware (and hence no validation). One of these is insert which accesses the Mongo driver directly.
Product.collection.insert({
item: "ABC1",
details: {
model: "14Q3",
manufacturer: "XYZ Company"
},
}, function(err, doc) {
console.log(err);
console.log(doc);
});
You can have multiple models that use the same collection, so create a second model without the required field constraints for use with CSV import:
var rawProduct = mongoose.model("RawProduct", Schema({
name: String,
price: Number
}), 'products');
The third parameter to model provides an explicit collection name, allowing you to have this model also use the products collection.
I was able to ignore validation and preserve the middleware behavior by replacing the validate method:
schema.method('saveWithoutValidation', function(next) {
var defaultValidate = this.validate;
this.validate = function(next) {next();};
var self = this;
this.save(function(err, doc, numberAffected) {
self.validate = defaultValidate;
next(err, doc, numberAffected);
});
});
I've tested it only with mongoose 3.8.23
schema config validateBeforeSave=false
use validate methed
// define
var GiftSchema = new mongoose.Schema({
name: {type: String, required: true},
image: {type: String}
},{validateBeforeSave:false});
// use
var it new Gift({...});
it.validate(function(err){
if (err) next(err)
else it.save(function (err, model) {
...
});
})

Resources