在Node.js+Express环境下,如何实现向MongoDB文档数组添加对象并解决update is not a function错误?
Let's break down what's going wrong here and fix it step by step:
Why You're Seeing the Error
The update is not a function error happens because await music.findOne(query) returns a single MongoDB document object (or null if no match is found) — this document doesn't have an update method. The update method belongs to the collection object, not individual documents.
Plus, there's a small mismatch between your request body and Mongoose Schema that we need to fix: your request uses a text field for the comment, but your Schema defines it as comment.
Solution 1: Using Native MongoDB Driver
If you want to stick with the native MongoDB driver approach in your code, here's the corrected route:
app.post('/addReview/:id', async (req, res) => { let idNumber = parseInt(req.params.id); // Fix the field mismatch and ensure correct data types const reviewObject = { id: parseInt(req.body.id), // Convert to number to match Schema locale: req.body.locale, rating: parseInt(req.body.rating), comment: req.body.text // Map 'text' from request to 'comment' in Schema }; try { const musicCollection = client.db('database').collection('music'); const query = { id: idNumber }; // Use updateOne on the collection, not the retrieved document const result = await musicCollection.updateOne( query, { $push: { reviews: reviewObject } }, { upsert: false } // Set to true if you want to create a new doc if none exists ); if (result.matchedCount === 0) { return res.status(404).json({ message: "Music entry not found" }); } res.status(200).json({ message: "Review added successfully" }); } catch (err) { console.error(err); res.status(500).json({ message: "Failed to add review", error: err.message }); } });
Key Notes for This Approach:
$pushhandles both cases: If thereviewsfield doesn't exist on the document,$pushwill automatically create a new array and add your review object to it. If the field already exists, it appends the object to the existing array — exactly what you need.- We converted fields like
idandratingto numbers to match your Schema's type requirements. - The
upsert: falseoption ensures we don't create a new music document if the requestediddoesn't exist (change totrueif you want that behavior).
Solution 2: Using Mongoose Model (Cleaner & Safer)
Since you already defined a Mongoose Schema and Model for Music, using it will give you automatic data validation and cleaner code:
First, make sure you import your Music model at the top of your route file:
const Music = require('./path/to/your/musicModel'); // Update the path to your model file
Then rewrite the route:
app.post('/addReview/:id', async (req, res) => { let idNumber = parseInt(req.params.id); // Prepare review data to match Schema const reviewData = { id: parseInt(req.body.id), locale: req.body.locale, rating: parseInt(req.body.rating), comment: req.body.text }; try { // Use findOneAndUpdate for atomic, validated updates const updatedMusic = await Music.findOneAndUpdate( { id: idNumber }, { $push: { reviews: reviewData } }, { new: true, // Return the updated document instead of the original runValidators: true, // Enforce Schema validation on the review data upsert: false } ); if (!updatedMusic) { return res.status(404).json({ message: "Music entry not found" }); } res.status(200).json({ message: "Review added successfully", data: updatedMusic }); } catch (err) { console.error(err); res.status(500).json({ message: "Failed to add review", error: err.message }); } });
Why This Is Better:
- Automatic Validation: The
runValidators: trueoption ensures your review data matches the Schema rules (e.g., no missing required fields, correct data types). - Atomic Operation:
findOneAndUpdatehandles the lookup and update in a single atomic step, which is safer than separatefindOneand update calls. - Cleaner Response: The
new: trueoption lets you return the updated document directly to the client.
内容的提问来源于stack exchange,提问作者user13914915

