I hope this is the right place to discuss the CRUD issues. So I'm building a MERN e-commerce app, where I created the mongoose schema and connected with MongoDB to store the products & users. To test my schema and routes I used Postman, and while other requests related to users were working as usual I faced a weird error in the case of adding new products since this is the most important feature.
I'm not sure what is this error and why is this error occurring.
This is my POST request body -
const Product = require("../models/Product");
const router = require("express").Router();
// CREATE PRODUCT
router.post("/", verifyTokenAndAdmin, async (req, res) => {
const newProduct = new Product(req.body);
try {
const savedProduct = await newProduct.save();
res.status(200).json(savedProduct);
} catch (err) {
res.status(500).json(err);
}
});
The verifyToken is a JWT token.
Here is the Schema
const mongoose = require("mongoose");
const ProductSchema = new mongoose.Schema(
{
prodId: {
type: String,
required: true,
},
prodName: {
type: String,
required: true,
},
brandName: {
type: String,
required: true,
},
img: {
type: Array,
},
color: {
type: Array,
},
size: {
type: Object,
},
fabricSpecs: {
type: String,
},
model: {
type: String,
},
descDetail: {
type: String,
},
price: {
type: Number,
required: true
},
discount: {
type: Boolean
},
discountAmount: {
type: Number
},
rating: {
type: String
},
review: {
type: Number
},
soldOut: {
type: Boolean
},
category: {
type: String,
},
type: {
type: String,
}
},
{
timestamps: true,
}
);
module.exports = mongoose.model("Product", ProductSchema);
Here is the error shown in the Postman while adding creating another product
Tried mongo shell also but getting the same error
The error indicates an entry with {title: null} already exists for the index title_1. It is most likely a unique index and you need to adjust the title if entry item is a variant under the same title.
I am really grateful to Doug Duncan from the MongoDB community who helped me in figuring out the problem.
So the actual issue as described by Dough was that apparently, I was having a title field with a unique index that was being populated every time I was inserting a new document. Now for the first index, everything was fine and the document got inserted successfully but after that, all the inserts were throwing this title field null error shown below:
What I am seeing is that you have a unique index on the title field for the products collection. This is not getting populated so a null value is getting passed to the document and there is already a document with a null value in the collection. You have a unique index on the title based on the error you’re getting. Run db.products.getIndexes() and you should see the index.
Ironically, I haven't inserted any field named "title" anytime in my product schema, so how does this happen?
It turns out that MongoDB will allow you to create indexes on fields that don’t exist. It seems that somehow a unique index got created on the title field at some time even though no documents would contain that field. Now this can be fixed by dropping that index everything should be good and you will be able to insert more than a single document without the duplicate key violation.
So I ran the db.products.getIndexes() command in the mongo shell and found that he was right, there was actually a "title" field with a unique index value.
According to Dough, this can be fixed by removing the title field using this command
db.products.dropIndex({"title": 1})
Thanks to Dough, after running this command I can insert multiple documents since the title field is removed now and the only unique index is productId, which I make sure is always unique during insertion.
More about dropIndex -
https://www.mongodb.com/docs/v4.4/reference/method/db.collection.dropIndex/?_ga=2.253559119.714913216.1662801611-1986988651.1652705652
Related
So I am running into an issue trying to add comments to some data that is already in my mongoDB database. I want to make it so I can have comments be added and removed and updated for each account saved in my database and I am not sure what I am doing wrong necessarily. I have set up my front end to send in a new comment and all the data that needs to come along with it. it successfully gets to my back end, and at my backend it says it runs through and it gets saved but it doesnt, and when i reload the page the comments array in my data is still 0.
Account.findByIdAndUpdate(
{ _id: comment.accountId },
{
$push: {Account: { comments: comment }},
},
{ new: true, upsert: true }
).exec(function (err, task) {
if (err) {
return res
.status(400)
.send({ message: "Failed to add comment due to invalid params" });
}
console.log("successfully uploaded comment");
return res.status(200).send(task);
});
so here we are loading the specific account and then pushing that new comment to the comments array that is in my mongoose schema. when I take out the "Account: object and just ahve the object with comments:comment, it says there is an internal error on the back end not finding the parameter i guess, which would be comments array.
I dont know why that would be an issue. below is my mongoose schema as well.
const AccountSchema = new Schema({
username: {
type: String,
},
name: {
type: String,
},
date: {
type: Date,
default: Date.now,
},
description: {
type: String,
},
latitude: {
type: Number,
},
longitude: {
type: Number,
},
comments: [
{
id: Number,
text: String,
type: String,
date: Date,
replies: [{ id: Number, text: String, type: String, date: Date }],
},
],
});
Am I not setting up my schema correctly? or am I forgetting to add something somewhere. any help would be great, thank you!
It looks like your update query's $push specification is to blame. In the code, it says:
$push: {Account: { comments: comment }}
But your Account model does not have an "Account" field to push an object into. To insert a new element into the "comments" array, do this instead:
$push: { comments: comment }
Just wanted to post an update, I changed around the options on the findbyidandupdate, i added new and safe and upsert all true, and then low and behold I realized that my main issue was that my comments array in my schema was set to type: string, and not array. lol thanks for the help guys.
I am trying to set 2 fields to being unique to each other and not have duplicates.
The code is this :
const Connection = mongoose.model("Connection", new mongoose.Schema({
from_friend: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Friend'
},
to_friend: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Friend'
}
}))
exports.Connection = Connection;
You can do this using a unique index that includes both fields
const ConnectionSchema = mongoose.Schema({
from_friend: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Friend'
},
to_friend: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Friend'
}
});
ConnectionSchema.index({ from_friend: 1, to_friend: 1 }, { unique: true });
module.exports = mongoose.model('Connection', ConnectionSchema);
The unique Option is Not a Validator
A common gotcha for beginners is that the unique option for schemas is not a validator. It's a convenient helper for building MongoDB unique indexes. See the FAQ for more information.
From the FAQ:
Q. I declared a schema property as unique but I can still save duplicates. What gives?
A. Mongoose doesn't handle unique on its own: { name: { type: String, unique: true } } is just a shorthand for creating a MongoDB unique index on name. For example, if MongoDB doesn't already have a unique index on name, the below code will not error despite the fact that unique is true.
var schema = new mongoose.Schema({
name: { type: String, unique: true }
});
var Model = db.model('Test', schema);
Model.create([{ name: 'Val' }, { name: 'Val' }], function(err) {
console.log(err); // No error, unless index was already built
});
However, if you wait for the index to build using the Model.on('index') event, attempts to save duplicates will correctly error.
You will need to write your own custom validator.
If the built-in validators aren't enough, you can define custom validators to suit your needs.
Custom validation is declared by passing a validation function. You can find detailed instructions on how to do this in the SchemaType#validate() API docs.
Sorry if title looks complicated... I couldn't think of a better way to describing it.
My real case situation matches the following Schemes:
Collection1:
const mongoose = require('mongoose');
const itemSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'] },
quantity: { type: Number, required: [true, 'Quantity is required.'] },
collection2: { type: mongoose.Schema.Types.ObjectId, ref: 'Collection2' }
}, { _id : false });
const collection1Schema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'] },
imagePath: { type: String, required: [true, 'Image is required.'] },
items: [itemSchema]
});
module.exports = mongoose.model('Collection1', collection1Schema);
Note: itemsSchema is inside the collection1 file (and having no declared _id's) because they only exist for the Collection1 model (considering "quantity" and other fields I removed for simplification). This itemsScheme is not needed elsewhere as another collection.
Collection2:
const mongoose = require('mongoose');
const collection2Schema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: [true, 'Name is required.'], unique: true }
});
module.exports = mongoose.model('Collection2', collection2Schema );
Note: Other properties (such as 'imagePath') were removed for simplification.
Now, this is the query I am trying to run:
Collection1.find()
.populate({
path: 'items',
populate: {
path: 'collection2', model: 'Collection2'
}
})
.then(...)
.catch(...);
And this is the error message I am getting when I run it:
Error fetching collection1: Cast to ObjectId failed for value "{
name: 'an item name',
quantity: 750
}" at path "_id" for model "Collection1"
The exact same error happens if I just run:
Collection1.find()
.populate('items')
.then(...)
.catch(...);
Maybe I cannot run .populate('items') because it has no declared model. If this is the case, how can I populate collection2 while querying collection1? Again, I cannot consider storing items in a separated collection.
But if I run:
Collection1.find()
.populate('collection2')
.then(...)
.catch(...);
I get the items, no errors, but it doesn't populate collection2. Well, it makes sense for the items because they're just an array of a block of properties inside collection1. But what about populating collection2?
Collection2 already has a few documents added, all with their _ids and other fields well filled. In the controller, I set _id: new mongoose.Types.ObjectId(), while creating a new document for both cases, Collection1 and Collection2.
In the front-end, I create a new document for Collection1, I add items, each item with a document from Collection2, and I save everything with no errors. I also confirmed everything is been properly saved (collection1 has list of items and each item an _id reference to collection2). The only problem is populating collection2 inside this array.
I have already tried restructuring everything with _ids (including itemScheme) and dropping all collections to test it again but no success.
I have been stuck with this problem for about three days now.
Is there any special property I should be setting for populate to make it work for this specific structure?
Thanks in advance...
populate('items')
This will not work as item is not a model.
What you want is following:
Collection1.find()
.populate('items.collection2')
.then(...)
.catch(...);
This will populate collection2 in all the array elements
This is my Schema:
let userSchema = new Schema({
email: { type: String, required: true },
password: { type: String, required: true },
books: [{ cover: String, title: String, link: String }],
});
I am pushing an object to the books array, only if that object does not already exist. I don't understand why this does not work: I am saying find the user, check the books array to ensure that title does not exist add the new object to books array. However duplicates still show up in my array.
note: The query is being executed inside a function which is passed model, user, and an object containing the book data called info hence the info.title.
model.findOneAndUpdate(
{ email: user.email, 'books.title': { $ne: 'info.title ' } },
{ $addToSet: { "books": { title: info.title, cover:
info.cover, link: info.link } } },
{ new: true },
(err, user) => {
if (err) throw err;
console.log(user);
}
)
Appreciate the direction
From MongoDB $addToSet documentation https://docs.mongodb.com/manual/reference/operator/update/addToSet/#behavior
If the value is a document, MongoDB determines that the document is a
duplicate if an existing document in the array matches the to-be-added
document exactly; i.e. the existing document has the exact same fields
and values and the fields are in the same order. As such, field order
matters and you cannot specify that MongoDB compare only a subset of
the fields in the document to determine whether the document is a
duplicate of an existing array element.
You can also find a good answer here MongoDB - $addToSet on a list of Embedded Document
I have the following schemas defined:
module.exports.contact=Schema({
_id:Number,
name: String,
email: String,
contactNumber: String,
company:String,
_invoices:[{ type: Schema.Types.ObjectId, ref: 'invoice' }],
},{strict:false});
module.exports.invoice=Schema({
_contact: { type : Number, ref: 'contact'},
_id:Number,
invoiceNumber:Number,
},{strict:false});
And following data is inserted in mongodb:
//CONTACT COLLECTION
{_id:1,name:"Mrinal Purohit",email:"mrinal#mrinalpurohit.com",contactNumber:"+919016398554",company:""}
//INVOICE COLLECTION
{ _id:1, invoiceNumber:3 , _contact:1 }
Only one document is there in the respective collections. Now i attempt this:
models.contact.find().populate('_invoices').exec(function(err,data){
console.log(data);
res.send(data)
});
I get the following:
[ { _id: 1,
name: 'Mrinal Purohit',
email: 'mrinal#mrinallabs.com',
contactNumber: '+919016398554',
company: '',
__v: 0,
_invoices: [] } ]
I actually expected the invoices to be populated in the array. Is there something wrong with the schema definition?
I am unable to completely understand the .populate function of mongoose :(
Your _id type is mismatched
In your invoice schema _id is defined as a Number. However in your definition of _invoices in the contact schema, you've selected the Schema.Types.ObjectId type
Try changing the type of _invoices to Number, e.g.
module.exports.contact=Schema({
_id:Number,
name: String,
email: String,
contactNumber: String,
company:String,
_invoices:[{ type: Number, ref: 'invoice' }],
},{strict:false});
Alternatively, you can let Mongoose set _id for you by omitting the _id property in your schemas (so you can leave _invoices unchanged). It would save you a bit of work in generating unique IDs and it also has a bunch of other useful properties (like the embedded timestamp)
Update
damphat is also correct in pointing out the other error (which I missed). According to the data you're inserting, you've haven't pushed the _id of your new invoice into the relevant contact document
Here's a sketch of how to create an invoice while properly updating the relevant contact
Invoice.create(invoiceAttributes, function (err, invoice) {
if (err) // Handle your error
Contact.findOne({ _id: invoiceAttributes._contact}, function (err, contact) {
if (err) // Handle your error
contact._invoices.push(invoice._id); // The important step you were missing
contact.save(); // Insert your callback
});
});
Just add 1 line _invoices: [1] to the contact document:
{
_id: 1,
name: "Mrinal Purohit",
email: "mrinal#mrinalpurohit.com",
contactNumber: "+919016398554",
company: "",
_invoices: [1]
}
and correct the typo in your contact schema as well.