Deleting a single item from a database with mongodb - node.js

{
"_id": {
"$oid": "5a2de0a00d6baa43e8b925d0"
},
"name": "test",
"playList": [
{
"url": "https://p.scdn.co/mp3-preview/8aa799e60164f8a1fb311188d9d85ef65d7782c6?cid=ed36a056ee504173a3889b2e55cbd461",
"artist": "Kenny G",
"songName": "My Heart Will Go On (Love Theme from \"Titanic\")",
"_id": {
"$oid": "5a2de0ad0d6baa43e8b925d1"
}
},
{
"url": "https://p.scdn.co/mp3-preview/7c49854f18e6dfda6cd97ab5e8bc139d7ca82b7c?cid=ed36a056ee504173a3889b2e55cbd461",
"artist": "PRODUCE 101",
"songName": "PICK ME",
"_id": {
"$oid": "5a2de13b0d6baa43e8b925d2"
}
}
],
"__v": 0
}
I have a database called channels where each channels contain a playList as shown below. I want to delete a single item when a button is clicked. I can handle the onClick event part, but I am not sure how to implement the routes part.
I know that I start by doing something like
router.delete(''/channels/:id', function(req, res){
something here...
})
but how can I access a particular item (probably with a unique id?) and delete it from the DB?
EDIT
By using the GET below
router.get('/channels/:id',
isLoggedIn,
function(req, res) {
channel.findOne({'name':req.params.id},function(err,channeldata){
if(err || channeldata === null){
res.status(404).send({
message: 'Channel Not Found',
data: []
})
}
else {
res.status(200).json({
message: "channel to "+req.params.id+"success",
data:channeldata
})
}
})
});
I get the data for a single channel in my DB.
But since I am new to this stuff, I am not sure how to access each item of the playList and delete a single data.
EDIT2
var mongoose = require('mongoose');
var ChannelSchema = new mongoose.Schema({
name: {type:String,required:true},
playList: [{
songName: { type : String },
artist: { type : String },
url: { type : String }
}]
})
module.exports = mongoose.model('Channel',ChannelSchema);

You can try the following snippet that contains the DELETE (part of CRUD) endpoint for your resource collection (i.e. the channels):
router.delete('/channels/playlist/song', isLoggedIn, (req, res) => {
const channel_id = req.query.channelId;
const song_id = req.query.songId;
// the following query deletes a song form a playlist of a certain channel
channel.update({_id: ObjectId(channel_id)},{$pull:{playList:{_id:ObjectId(song_id)}}})
.exec()
.then(result => {
// for checking if document was found and deleted
// mongodb actually returns special object `result`
// which has its own certain fields
res.status(200).send({
status: "success",
message: result
});
})
.catch(error => {
// here we see if we had any problem with server or db itself
console.log(error)
res.status(500).send({
success: false,
message: "Something went wrong with DELETE /channels/:id"
})
})
});
I assume that you know what ObjectId() function does
if you do not have it declared, declare the following comment
in the beginning of the file (where you require everything)
const mongoose = require('mongoose'); // you must have this
const ObjectId = mongoose.Types.ObjectId; // gets the function
Let me know if this helps, or if you do not understand something - I will make an edit so that you get it.

Related

update controller fails to works as intended

I'm trying to make a nodejs\ExpressJs backend for a small project. The backend is split into services, controllers and router files. While testing the updateProduct (to update the fields of a product that already exists in database) function on postman using a x-www-form-urlencoded body, I got the following error:
{
"err": {
"stringValue": "\"{ name: 'Hp', description: 'Pavillion', price: '20000' }\"",
"valueType": "Object",
"kind": "ObjectId",
"value": {
"name": "Hp",
"description": "Pavillion",
"price": "20000"
},
"path": "_id",
"reason": {},
"name": "CastError",
"message": "Cast to ObjectId failed for value \"{ name: 'Hp', description: 'Pavillion', price: '20000' }\" (type Object) at path \"_id\" for model \"Product\""
}
}
Screen of postman form :
This is the code I used in their respective files, I tried logging a message into the console to see whether they are called upon, and yes they were called as intended.
//service file
const updateProduct = async(product)=>{
return await Product.findByIdAndUpdate(product._id, product)
}
//controller file
const updateProduct = async(req, res)=>{
try{
const result = await productService.updateProduct(req.body)
res.status(200).json(result)
}
catch(error){
res.status(500).json({err:error})
}
}
//router file
router.put("/:id", productController.updateProduct)
I tried changing the code in the service layer to the following, it didn't work, but it not generate the same problem.
const updateProduct = async(product) => {
return await Product.findByIdAndUpdate(
product._id,
{
$set: {
name: product.name,
price: product.price,
description: product.description
}
},
{ new: true }
);
};
If relevant I'm using Mongoose and other methods such as adding or deleting work as intended. Thanks
Problem:
You have to get the _id of the desired product from the params not from the req.body which return null like:
this:
return await Product.findByIdAndUpdate(product._id, product);
would turn into:
return await Product.findByIdAndUpdate(undefined, {
"name": "Hp",
"description": "Pavillion",
"price": "20000"
})
and of course, the record won't get updated.
Solution:
your controller should look like this (I've used the path):
//controller file
const updateProduct = async(req, res)=>{
try{
const result = await productService.updateProduct(req.params.id, req.body)
res.status(200).json(result)
}
catch(error){
res.status(500).json({err:error})
}
}
and your service should look like this:
//service file
const updateProduct = async(id, product)=>{
return await Product.findByIdAndUpdate(id, product)
}
``

MERN - update specific string in an array's object

I am using mongoose to connect my backend (Express) server to database. I want to do normal CRUD operations - but I am able to do it only for direct data in object, but I need to be able to access also array data.
Example of my model:
const LeewaySchema = new mongoose.Schema({
name: {
type: String,
},
shirt: [
{
name: String,
image: String,
},
],
With the following code I am able to update only name of the object, but I need to be able to update also name in shirt array
Here is working approach when changing name of object:
app.put('/update', async (req, res) => {
const updateName = req.body.updateName;
const id = req.body.id;
console.log(updateName, id);
try {
await ClosetModel.findById(id, (error, closetToUpdate) => {
closetToUpdate.name = updateName;
closetToUpdate.save();
});
} catch (err) {
console.log(err);
}
res.send('success');
});
And I tried the same with shirt array, just specifying the correct path
app.put('/update-shirt', async (req, res) => {
const updateShirtName = req.body.updateShirtName;
const id = req.body.id;
try {
await ClosetModel.findById(id, (error, closetToUpdate) => {
closetToUpdate.shirt.name = updateShirtName; // different path here
closetToUpdate.save();
});
} catch (err) {
console.log(err);
}
res.send('success');
});
The server crashes and /update-shirt conflicts with /update path
I am using the same route and frontend for READ
useEffect(() => {
axios
.get('http://localhost:8000/read')
.then((response) => {
setListOfClosets(response.data);
})
.catch(() => {
console.log('error');
});
}, []);
And update name function calling with button onClick:
const updateCloset = (id) => {
const updateName = prompt('Enter new data');
axios
.put('http://localhost:8000/update', {
updateName: updateName,
id: id,
})
.then(() => {
setListOfClosets(
listOfClosets.map((val) => {
return val._id === id
? {
_id: id,
name: updateName,
email: val.email,
}
: val;
})
);
});
};
I don't really know how to do update for shirt's name, I tried to copy paste and just change path and url of course, but it did not work.
The question doesn't actually describe what specific transformation (update) you are attempting to apply to the document. Without knowing what you are attempting to do, there is no way for us to help advise on how to do it.
Say, for example, that the document of interest looks like this:
{
_id: 1,
shirt: [
{ name: "first shirt", image: "path to first shirt" },
{ name: "second shirt", image: "path to second shirt" },
{ name: "third shirt", image: "path to third shirt" }
]
}
Also let's say that the application hits the /update-shirt endpoint with an id of 1 and a updateShirtName of "updated shirt name". Which entry in the array is that string supposed to be applied to? Similarly, how would that information be passed to the server for it to construct the appropriate update.
It is absolutely possible to update documents in an array, here is some documentation about that specifically. But the actual structure of the command depends on the logic that you are attempting to provide from the application itself.
The only other thing that comes to mind here is that the motivation for the schema described in the question seems a little unclear. Why is the shirt field defined as an array here? Perhaps it should instead just be an embedded document. If so then the mechanics of updating the field in the subdocument are more straightforward and none of the aforementioned concerns about updating arrays remain relevant.
just make an update api where you just have to pass the id and and pass the shirt in the findByIdAndUpdate query and hit the postman by passing the below code.
shirt: [
{
name: "jhrh",
image: String,
},
],

How can I add multiple object references prior to saving a record using Mongoose and Node?

I am saving a record to my MongoDB database and am encountering a multiple save error when I attempt to add multiple object references to the record.
I have tried to change the way my code handles promises with .then and async/await functions but I do not think I am addressing the root cause of the problem.
Here is the code I am working with.
MENU SCHEMA
const MenuSchema = new Schema({
description: String,
title: String,
notes: String,
_menuItems: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "menuItems"
}
]
});
mongoose.model("menus", MenuSchema);
module.export = MenuSchema;
MENU ITEM SCHEMA
const MenuItemSchema = new Schema({
imageURL: String,
name_en: String,
name_es: String,
type_en: String,
type_es: String,
description_en: String,
description_es: String,
dietaryCallouts: [String],
price: Number
});
mongoose.model("menuItems", MenuItemSchema);
module.export = MenuItemSchema;
MENU CONTROLLER
create(req, res, next) {
try {
const { description, title, notes } = req.body;
const { _menuItems } = req.body;
const menu = new Menu({
description,
title,
notes,
_menuItems: []
});
let counter = 0;
_menuItems.forEach(function(i) {
counter++;
MenuItem.findOne({ _id: i }).then(menuItem => {
menu._menuItems.push(menuItem);
if (counter === _menuItems.length) {
menu.save().then(menu => {
res.send(menu);
});
}
});
});
} catch (err) {
res.status(422).send(err);
}
}
The expected result of the code is to loop through all of the menuItems ids, find them in their respective model, push them into the _menuItems array, and after all items have been added, save the menu object.
The error message that I receive is: "ParallelSaveError: Can't save() the same doc multiple times in parallel."
The object is saved, but only 1 of the menu items have been saved to the database instead of all of them.
For some reason the save function is getting called multiple times even though the logic flow should only execute once.
Any help would be much appreciated!
As I understand, you are trying to create a new menu with the given menuItems.
So you do it simply like this: (there is no need to loop)
router.post("/create-menu-and-items", async (req, res, next) => {
try {
const { description, title, notes, _menuItems } = req.body;
let menu = new Menu({ description, title, notes, _menuItems });
menu = await menu.save();
res.status(201).send(menu);
} catch (err) {
console.log(err);
res.status(422).send(err);
}
});
With this when you post to this url, response will be like this ( I assume you will send the existing menu item ids in the body:
{
"_menuItems": [
"5dc81674ed5a8a3f78356779",
"5dc81680ed5a8a3f7835677a",
"5dc8168bed5a8a3f7835677b"
],
"_id": "5dc8180990b714224462fb2b",
"description": "Menu 1 description",
"title": "Menu 1 title",
"notes": "Menu 1 notes",
"__v": 0
}
This will be extra info, but if in the future you want to add menu items to an existing menu, you can do like this:
router.post("/add-menu-items-to-menu/:id", async (req, res, next) => {
try {
const { _menuItems } = req.body;
let menu = await Menu.findByIdAndUpdate(req.params.id, { $push: { _menuItems } }, { new: true });
if (menu) {
res.status(200).send(menu);
} else {
res.status(400).send("Menu not found for this id");
}
} catch (err) {
console.log(err);
res.status(422).send(err);
}
});
Body:
{
"_menuItems": ["5dc81694ed5a8a3f7835677c", "5dc816a5ed5a8a3f7835677d"]
}
Response:
{
"_menuItems": [
"5dc81674ed5a8a3f78356779",
"5dc81680ed5a8a3f7835677a",
"5dc8168bed5a8a3f7835677b",
"5dc81694ed5a8a3f7835677c",
"5dc816a5ed5a8a3f7835677d"
],
"_id": "5dc81b134c92ae2884c2468c",
"description": "Menu 1 description",
"title": "Menu 1 title",
"notes": "Menu 1 notes",
"__v": 0
}

Mongoose updating document with findOne() matches only wrong results

I have a collection of fixtures that 'belong' to a competitor and look something like this:
{
"_id": {
"$oid": "59dbdf6dbe628df3a80419bc"
},
"timeOfEntrance": "1507581805813",
"timeOfFinish": null,
"competitor": {
"$oid": "59db5a3f3d6119e69911a61a"
},
"__v": 0
}
My goal is to update only the document's timeOfFinish by sending a PUT request with competitor's ID as a param in the url and timestamp in the body. However I'm struggling to compose the update query.
The following is what I have currently, it never finds the right match and to my surprise it's always updating the wrong document.
fixtureController.put = (req, res) => {
const competitorId = req.params.id;
const timeOfFinish = req.body.timeOfFinish;
Fixture.findOne({'competitor.$oid': competitorId}, (err, fixture) => {
fixture.set({ timeOfFinish });
fixture.save()
.then(updatedFixture => {
return res.status(200).json({
success: true,
fixture: updatedFixture
})
})
.catch(err => {
return res.status(500).json({
message: err
});
});
});
};
Bit of a beginner in the MongoDB field, will appreciate your comments and solutions.
Turns out there was no need to specify the exact field in the match parameter. Mongoose matches by the id field automatically.
Fixture.findOne({'competitor': competitorId}, (err, fixture) => {
...
});

req.params returns a part of the value

I have a React app that is posting to my Node/Express api. Posting is fine, but now I'm on delete and I cannot figure out why the req.params returns only a portion of the title string value. This seems like a fundamental thing that I am just not aware of, so go easy on me if it is a ridiculous question.
Here is my post, which works fine:
router.route('/movies')
.post(function(req,res) {
var movie = new Movie();
movie.title = req.body.title;
movie.genre = req.body.genre;
movie.actors = req.body.actors;
movie.year = req.body.year;
movie.rating = req.body.rating;
movie._id = new Date().getTime();
//save the movie and checkfor errors
movie.save(function(err) {
if (err) {
res.send(err);
} else {
res.json({message: "Movie created!"});
}
});
})
Here is the postman result of a GET after POST:
[
{
"_id": "586d94bf3175695e48f24fbd",
"rating": "5/5",
"year": "1981",
"genre": "Thriller",
"title": "The Godfather",
"__v": 0,
"actors": [
"[Robert DeNiro, Al Pacino]"
]
},
]
Notice the title string is "The Godfather". Now, here is my DELETE:
.delete(function(req,res) {
console.log('Title: ', req.params)
var query = {title: req.params.title};
Movie.findOneAndRemove(query, function(err, movie) {
if (err) res.send(err);
if(!movie) {
res.json({message: "No Movie found with that title"});
} else {
res.json({message: "Successfully deleted Movie"});
}
});
});
You will see my console.log in there, which logs as:
Title: { title: ' Godfather' }
I have tried JSON.stringify the param.title etc but I think I am just doing something simply wrong here that is evading me the more I stare at the same code.
EDIT
Here is the client code making the POST. The DELETE route I am testing in Postman by using the following url:
http://localhost:8080/api/movies/:The Godfather
handleSubmit() {
let movie = {
title: this.title.value,
genre: this.genre.value,
year: this.year.value,
actors: this.actors.value.split(','),
rating: this.rating.value
}
$.post( "http://localhost:8080/api/movies", movie );
The DELETE is working just fine. My Postman url had a colon included in it, which had an impact that sent me in another direction. Basically, when I changed the original url from this:
http://localhost:8080/api/movies/:The Godfather
To this:
http://localhost:8080/api/movies/The Godfather
It worked just fine.

Resources