Patch Request Method in Node.js and Mongoose - node.js

For update I use the following code that works:
router.put('/:id', async (req, res) => {
const { error } = validateProduct(req.body);
if (error) return res.status(400).send(error.details[0].message);
const product = await Product.findByIdAndUpdate(req.params.id,
{
name: req.body.name,
description: req.body.description,
category: req.body.category,
tags: req.body.tags,
withdrawn: req.body.withdrawn,
extraData: {
brand: req.body.extraData.brand,
quantity: req.body.extraData.quantity,
type: req.body.extraData.type
}
},
{new: true}
);
if (!product) return res.status(404).send('The product with the given ID was not found.');
res.send(product);
});
What I want to do is to create a Patch operation that updates only certain fields and not all of them as the update above. These fields are not standard but they are one of the above fields of the update operation.

You can try this snippet (didn't test it locally though). The idea is to only update those fields in Product, which were mentioned in req.body. Be sure your validator is secure, otherwise you can end up with nosql injection.
router.put('/:id', async (req, res) => {
const { error } = validateProduct(req.body);
if (error) return res.status(400).send(error.details[0].message);
const product = await Product.findById(req.params.id).exec();
if (!product) return res.status(404).send('The product with the given ID was not found.');
let query = {$set: {}};
for (let key in req.body) {
if (product[key] && product[key] !== req.body[key]) // if the field we have in req.body exists, we're gonna update it
query.$set[key] = req.body[key];
const updatedProduct = await Product.updateOne({_id: req.params.id}, query}).exec();
res.send(product);
});
Also I'm sure you can leverage lodash in the line, where I you use a for-in loop ;)
The downside of this approach is that it takes 2 queries to mongo, because you need a real document to compare the thing. Also update operation doesn't trigger post save hooks of your model. If you need them - you should findById() first, update necessary fields and then hit .save() on the very document you found.
Hope it helps

You could do this as well:
// define your middlewares here
// validateObjectId.js
const mongoose = require('mongoose');
module.exports = function (req, res, next) {
if (!mongoose.Types.ObjectId.isValid(req.params.id))
return res.status(404).send("Invalid ID.");
next();
}
// 404.js
module.exports = function (str, id) {
if (!str || !id) throw new Error('the string name and id must be defined');
return `The ${str} with the given ID (${id}) was not found`;
}
// import your middlewares into your product route
const validateObjectId = require('../middleware/validateObjectId'); // respect your middleware path
const fourOfour = require('../middleware/404'); // respect your middleware path
router.put('/:id', validateObjectId, async (req, res) => {
const { error } = validateProduct(req.body);
const { name, description, category, tags, withdrawn, extraData, } = req.body
if (error) return res.status(400).send(error.details[0].message);
let product = await Product.findById(req.params.id).exec();
if (!product) return res.status(404).send(fourOfour("Product", req.params.id));
product = await Product.findByIdAndUpdate(req.params.id, {
name: name || product.name,
description: description || product.description,
category: category || product.category,
withdrawn: withdrawn || product.withdrawn,
extraData: extraData || product.extraData
}, { new: true });
product = await product.save();
if (!product) return res.status(404).send(fourOfour("Product", req.params.id));
res.send(product);
});

Related

throw new error_1.MongoInvalidArgumentError('Update document requires atomic operators');

I'm getting (throw new error_1.MongoInvalidArgumentError('Update document requires atomic operators'); )
this type of error
Here is the full code for put endpoint:
app.put('/todo/:id', async (req, res) =>
{
const id = req.params.id; const data = req.body;
console.log(data);
const filter = { _id: ObjectId(id) };
const options = { upsert: true };
const updateDoc = { $set: { name: data.name, message: data.message, }, };
const result = await dataCollections.updateOne(filter, options, updateDoc);
res.send(result);
});
You are sending the parameters in the wrong order, update document comes before options, try this:
const result = await dataCollections.updateOne(filter, updateDoc, options);

How to Delete document and sub documents referenced from others collections - MongoDB Mongoose

I have this collection Cart (cart schema) to delete and it is referenced with 2 other schemes, Meal and Customer (owner user, its schema is: User Schema).
How can I delete the cart by passing as req.params.id the user's id from the HTTP request?
Cart Schema
const mongoose = require('mongoose');
const idValidator = require('mongoose-id-validator');
const Schema = mongoose.Schema;
const cartItemSchema = new Schema ({
quantity: { type: Number, required: true },
itemId: { type: mongoose.Types.ObjectId, required: true, ref: 'Meal' }
});
const cartSchema = new Schema ({
cartItems : [
cartItemSchema
],
customer: { type: mongoose.Types.ObjectId, required: true, ref: 'User'}
});
cartSchema.plugin(idValidator);
module.exports = mongoose.model('Cart', cartSchema);
I created a function to delete the document, but it doesn't work, it returns the message: 'Deleted cart.', but isn't true, the document remains in collection.
const deleteCartByUserId = async (req, res, next) => {
const userId = req.params.uid;
let cart;
try {
cart = await Cart.find({ customer: userId });
} catch(err) {
const error = new HttpError('Something went wrong, could not delete cart.', 500);
return next(error);
}
if(!cart) {
const error = new HttpError('Could not find cart for this user id.', 404);
return next(error);
}
try {
Cart.deleteOne({ customer: userId });
} catch(err) {
console.log(err);
const error = new HttpError('Something went wrong, could not delete cart.', 500);
return next(error);
}
res.status(200).json({ message: 'Deleted cart.' });
};
So the porblem was that you missed an await before delete one function call.
Also I've changed some of youre code to make it cleaner:
const functionHandler = fn =>
(req, res, next) =>
Promise
.resolve(fn(req, res, next))
.catch(next);
const deleteCartByUserId = functionHandler(async (req, res) => {
const { params: { uid: userId } } = req;
const cart = await Cart.findOneAndDelete({ customer: userId })
if(!cart) {
throw new HttpError('Could not find cart for this user id.', 404);
}
res.status(200).json({ message: 'Deleted cart.' });
});
In your error handler middleware you can check for error type and if it's not HttpError use internal server error.

array of objects won't POST into mongodb

I have an array of objects that is defined in mongoose schema as
blacklistGroup: {
userId: { type: String },
username: { type: String }
}
I can't figure out why it won't POST into mongodb.
I have a console.log that shows that it represents it's schema, but it never appears in mongodb? What am I doing wrong?
console.output
req.body.blacklistGroup
[ { userId: '5e2350c7f88cfb331c4f67de', username: 'artist1' },
{ userId: '5e20c5a139a92512cc7df63c', username: 'artist' } ]
[object Object]
app.js
app.post("/api/listings", checkAuth, (req, res, next) => {
console.log("req.body.blacklistGroup");
console.log(req.body.blacklistGroup);
let blacklistGroup = req.body.blacklistGroup;
console.log("req.body.blacklistGroup");
const post = new Post({
blacklistGroup: req.body.blacklistGroup,
});
//saves to database with mongoose
post.save().then(result => {
console.log(result);
res.status(201).json({
message: "Auction listing created successfully!",
postId: result._id
});
});
});
You can store all user at once. use mongoose insertMany
const Post = require('post'); //mongoose schema
app.post("/api/listings", checkAuth,(req, res, next) => {
console.log("req.body.blacklistGroup");
console.log(req.body.blacklistGroup);
let blacklistGroup = req.body.blacklistGroup;
console.log("req.body.blacklistGroup");
const blacklistGroup = req.body.blacklistGroup;
(async function(){
await Post.insertMany(blacklistGroup);
res.status(200).send('Ok');
})();
});
Or you can use
const Post = require('post'); //mongoose schema
app.post("/api/listings", checkAuth,async (req, res, next) => {
console.log("req.body.blacklistGroup");
console.log(req.body.blacklistGroup);
let blacklistGroup = req.body.blacklistGroup;
console.log("req.body.blacklistGroup");
const blacklistGroup = req.body.blacklistGroup;
await Post.insertMany(blacklistGroup);
res.status(200).send('Ok');
});
For More Here
You don't have an array of objects (or at least you don't want one), you have an object with two properties; userId and username. MongoDB is expecting JSON and it looks like you're trying to send it an array containing that object.
Try this:
let blacklistGroup = req.body.blacklistGroup[0];
To process an array of objects passed as req.body.blacklistGroup, you will have to iterate over it, define a new Post for each object and then send it. I think part of the confusion here is that your Schema is called blacklistGroup but it doesn't refer to a group, it refers to one entry.
const dbCalls = blacklistGroup.map(userObject => {
const post = new Post({
blacklistGroup: {
userId: userObject.userId,
username: userObject.username
});
return new Promise((resolve, reject) => {
post.save.then(() => {
resolve();
})
.catch(err => {
reject(err);
})
})
});
Promise.all(dbCalls).then(() => {
res.status(201).json({message: "Auction listings created successfully!"})
})
.catch(err => {
res.status(500).json({error: err})
});

access the info of the user that created the post

I am making a react-native mobile app and I am having trouble passing the users info that created the post to the home page in the post detail. I can pass the userID but for some reason when I add the rest of the info to the payload I can't create a post. Please help.
BACKEND
This is the requireAuth file that requires authentication before performing a tast. My code for the user is here as well at the bottom---
const mongoose = require("mongoose");
const User = mongoose.model("User");
module.exports = (req, res, next) => {
const { authorization } = req.headers;
if (!authorization) {
return res.status(401).send({ error: "You must be logged in." });
}
const token = authorization.replace("Bearer ", "");
jwt.verify(token, "mySecretKey", async (err, payload) => {
if (err) {
return res.status(401).send({ error: "You must be logged in." });
}
const { userId, name, phone, email } = payload;
const user = await User.findById(userId);
req.user = user;
console.log(req.user);
next();
});
};
This is the POST route for the Item---
router.post("/items", requireAuth, async (req, res) => {
const { title, category, detail, condition, price } = req.body;
if (!title || !category || !detail || !condition || !price) {
return res.status(422).send({
error: "You must provide a title, category, detail, condition, and price"
});
}
try {
const item = new Item({
title,
category,
detail,
condition,
price,
userId: req.user._id
});
await item.save();
res.send(item);
} catch (err) {
res.status(422).send({ error: err.message });
}
});
FRONT-END
This is my createItem function in the itemContext file---
const createItem = dispatch => async ({
title,
category,
detail,
condition,
price
}) => {
try {
const response = await sellerApi.post("/items", {
title,
category,
detail,
condition,
price
});
//this is the other place the error might be happening i need this to save in the phone local storage
dispatch({ type: "create_item", payload: response.data });
navigate("Home");
} catch (err) {
console.log(err);
}
};
All I am trying to do it is when the post is being displayed so is the info of the post creator
For existing post in the database: If you are referencing your user in post model like this
const Post = mongoose.model('Post', {
// other fields
userId: {
type: mongoose.Schema.Types.ObjectId,
required: true,
ref: 'User'
}
})
then you can use populate to fetch user of that post.
const post= await Post.findById('5c2e505a3253e18a43e612e6')
await post.populate('userId').execPopulate()
console.log(post.userId)

findOne filtering by objectId always returns undefined

Developing node.js app. For some reason this findOne call always returned undefined, even though I have verified that the req.params.id is valid and it should return data...
let event = await Event.findOne({ venue: ObjectId(req.params.id) });
I've also tried....
let event = await Event.findOne({ venue: req.params.id });
Here's part of my model definition for Event....
const EventSchema = new mongoose.Schema({
eventDate: {
type: Date
},
venue: {
type: mongoose.Schema.ObjectId,
ref: 'Venue'
},
In postman I am doing a DELETE http verb against /venues/5e0401c4a246333ca4ced332 url for your information.
Basically I'm wanting to search for the venue id in the events table. I'm seeing if this venue is being used by an event before I decide to delete it.
Here's the entire method...
// #desc Delete venue
// #route DELETE /api/v1/venues/:id
// #access Private
exports.deleteVenue = asyncHandler(async (req, res, next) => {
let venue = await Venue.findById(req.params.id);
if (!venue) {
return next(
new ErrorResponse(`No venue with the id of ${req.params.id}`, 404)
);
}
if (req.user.role !== 'manager' && req.user.role !== 'admin') {
return next(new ErrorResponse(`Not authorized to delete venue`, 401));
}
// the following line always returns undefined even though the venue
// exists in the events table.... then it jumps to the end of the method
let event = await Event.findOne({ venue: ObjectId(req.params.id) });
if (event) {
return next(
new ErrorResponse(
`You cannot delete a venue because events are tied to it.`,
404
)
);
}
await Venue.remove();
res.status(200).json({
success: true,
data: {}
});
});
Using Compass, looking at the events collection I definitely see records using the id I submitted...
_id:5e045b6e0c38f2502440ecb7
attendees:Array
eventDate:2020-01-01T05:00:00.000+00:00
venue:5e0401c4a246333ca4ced332
status:"planning"
I did...
console.log(`id is ${req.params.id}`);
let event = await Event.findOne({ venue: req.params.id });
The results showed I was passing the correct id.... (console output)
id is 5e0401c4a246333ca4ced332
ReferenceError: Event is not defined
at C:\Projects\abc\controllers\venues.js:90:15
Any insight would be appreciated. Thanks!
It seems you are not importing your Event model.
You need to add:
const Event = require("../models/event"); //you need to change the path
After that you need to find the event like this:
Event.findOne({ venue: req.params.id }).
Also I have a few suggestions:
1-) It makes more sense to check role authorization in the beginning.
2-) First check if there is any Event for the given Venue, and if there is no events, use findByIdAndDelete method. This way we can reduce number of db access, since we eliminated Venue.findById
3-) In the case a venue has events, using a 404 status code does not seem correct.
I think 400 - Bad request is more appropriate.
4-) mongoose converts _id's, so there is no need to use ObjectId.
exports.deleteVenue = asyncHandler(async (req, res, next) => {
if (req.user.role !== "manager" && req.user.role !== "admin") {
return next(new ErrorResponse(`Not authorized to delete venue`, 401));
}
let event = await Event.findOne({ venue: req.params.id });
if (event) {
return next(new ErrorResponse(`You cannot delete a venue because events are tied to it.`,400));
}
let venue = await Venue.findByIdAndDelete(req.params.id);
console.log(venue);
if (!venue) {
return next(new ErrorResponse(`No venue with the id of ${req.params.id}`, 404));
}
res.status(200).json({
success: true,
data: {}
});
});
Test:
Let's say we have the following two Venue document.
{
"_id" : ObjectId("5e0471e3394d1e2b348b94aa"),
"name" : "Venue 1",
},
{
"_id" : ObjectId("5e0471eb394d1e2b348b94ab"),
"name" : "Venue 2",
}
And one Event document whose venue is Venue 1 with id 5e0471e3394d1e2b348b94aa:
{
"_id" : ObjectId("5e04727c76da213f8c9bf76a"),
"eventDate" : ISODate("2019-12-26T11:41:16.019+03:00"),
"name" : "Event 1",
"venue" : ObjectId("5e0471e3394d1e2b348b94aa")
}
When we want to delete the Venue 1, this will result with the following error, because it has an event:
You cannot delete a venue because events are tied to it. with status code 400.
Do it with findOneAndDelete of mongoose and Expressjs:
const express = require("express");
const router = express.Router();
router.delete("/:id", (req, res) => {
if (!mongoose.Types.ObjectId.isValid(req.params.id)) { //checking if id valid
return res.send("Please provide valid id");
}
var id = mongoose.Types.ObjectId(req.params.id); // passing it into a var
Event.findOneAndDelete({ venue: id }).then( res.json({ success: true }));
});
module.exports = router;
for details https://mongoosejs.com/docs/api/query.html#query_Query-findOneAndDelete
exports.deleteVenue = asyncHandler(async (req, res, next) => {
//First check role. Because it not call database
if (req.user.role !== 'manager' && req.user.role !== 'admin') {
return next(new ErrorResponse(`Not authorized to delete venue`, 401));
}
//Next step, check event
const event = await Event.findById({ venue: ObjectId(req.params.id) });
if (event) {
return next(
new ErrorResponse(
`You cannot delete a venue because events are tied to it.`,
404
)
);
}
//Delete venue by Id
await Venue.deleteOne({ id: req.params.id }, (error, result) =>{
if(error) return next(
new ErrorResponse(`No venue with the id of ${req.params.id}`, 404)
);
res.status(200).json({
success: true,
data: {}
});
})
});

Resources