How to push data to another schema in nodejs mongodb - node.js

thanks for click my question..
I have two schemas in mongodb, citySchema and destinationSchema. I want to make one to many relation. Each destination has one city and the city has many destinations.
This is my destinationSchema
var mongoose = require("mongoose");
var destinationSchema = new mongoose.Schema({
name: String,
image: String,
description: String,
city: {
type: mongoose.Schema.Types.ObjectId,
ref: "City"
}
});
module.exports = mongoose.model("Destination", destinationSchema);
This is citySchema
var mongoose = require("mongoose");
var citySchema = new mongoose.Schema({
name: String,
image: String,
description: String,
destinations: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Destination"
}
]
});
module.exports = mongoose.model("City", citySchema);
And this is the post route to create new destination
router.post("/", function(req, res) {
var name = req.body.name;
var image = req.body.image;
var description = req.body.description;
var city = req.body.city;
var newDestination = {
name: name,
image: image,
description: description,
city: city
};
Destination.create(newDestination, function(err, newCreatedDestination) {
if (err) {
console.log(err);
} else {
res.redirect("/admin/destinations");
}
});
});
This is my form to create a new destination
<select name="city">
<% cities.forEach(function(city) { %>
<option value=<%=city._id%> > <%= city.name %> </option>
<% }) %>
</select>
It works fine. But I want when i create a new destination, it push current destination id to city schema (destinations array).
Sorry for my bad English. I appreciate every your answers and suggestions. And Thank you 😊

After you create a new destination, you can push that destination's id to the city destinations.
You can use the following route:
router.post("/", async (req, res) => {
try {
const { name, image, description, city } = req.body;
let newDestination = { name, image, description, city };
newDestination = await Destination.create(newDestination);
let result = await City.findByIdAndUpdate(
city,
{
$push: { destinations: newDestination._id }
},
{ new: true }
);
res.redirect("/admin/destinations");
} catch (err) {
console.log(err);
res.status(500).send("Something went wrong");
}
});
Let's have an existing city like this:
{
"destinations": [],
"_id": "5e071f212e9ecd31508785c6",
"name": "Padang",
"image": "image 1",
"description": "beautiful city",
"__v": 0
}
If we want to add this city a destination, we can send this request body to our post route. Please note that as city value, we need to use an existing city id (5e071f212e9ecd31508785c6) like the one we have already.
{
"name": "destination name",
"image": "destination image",
"description": "destination description",
"city": "5e071f212e9ecd31508785c6"
}
The result will be like this:
A new destionation is created:
{
"_id" : ObjectId("5e071fd51cd3600bb083b5e7"),
"name" : "destination name",
"image" : "destination image",
"description" : "destination description",
"city" : ObjectId("5e071f212e9ecd31508785c6"),
"__v" : NumberInt(0)
}
And added to the city:
{
"_id" : ObjectId("5e071f212e9ecd31508785c6"),
"destinations" : [
ObjectId("5e071fd51cd3600bb083b5e7")
],
"name" : "Padang",
"image" : "image 1",
"description" : "beautiful city",
"__v" : NumberInt(0)
}

After creating a destination, use mongoose findOneAndUpdate method to update the relevant city.
As the name implies, findOneAndUpdate() finds the first document that
matches a given filter, applies an update, and returns the document.
Mongoose findOneAndUpdate

Related

Document is inserted twice into MongoDB when using Mongoose create method

I am having below schema in my API which gets details of username and the products he added to the cart.
const mongoose = require('mongoose');
mongoose.connect('mongodb connection').then(()=>{
console.log('DB connection is successfull');});
const AutoIncrement = require('mongoose-sequence')(mongoose);
const cartSchema = new mongoose.Schema({
cartId : {
type : Number
},
username : {
type : String
},
productsInCart : [{
productId : {type : Number,required : true},
productName : {type:String},
quantity : {type:Number}
}],
statusOfCart : {
type : String,
default : 'Open'
}},{ timestamps: true });
cartSchema.plugin(AutoIncrement,{id : 'cart_seq',inc_field : 'cartId'});
let cartModel = mongoose.model('carts',cartSchema);
module.exports = cartModel;
As you can see in the above code I am also using the mongoose-sequence to make cartId as a auto-incremented field.
Now, I have a POST request which gets the below JSON in request body and adds it to the cart collection in the MongoDB using the create method.
{
"username":"admin",
"productsInCart":
[
{
"productId":1,
"productName":"Watch",
"quantity":4
},
{
"productId":2,
"productName":"Phone",
"quantity":5
}
]
}
The code inside the Route Handler for the POST request in Express API would look something like this
let ctMod = new cartModel();
ctMod.username = req.body.username;
ctMod.productsInCart = req.body.productsInCart;
let insCartData = await cartModel.create(ctMod,{new:true});
if(insCartData.length > 0)
{
return res.status(200).json({
message : `New items got inserted into the cart with the ID : ${insCartData.cartId}`,
data : insCartData
});
}
The above code inserts two entries into the collection like below instead of one
{
"statusOfCart": "Open",
"productsInCart": [],
"createdAt": "2021-01-04T15:25:35.188Z",
"updatedAt": "2021-01-04T15:25:35.188Z",
"cartId": 13,
"__v": 0
},
{
"statusOfCart": "Open",
"productsInCart": [
{
"_id": "5ff332a891aa170b60a21ea9",
"productId": 1,
"productName": "Watch",
"quantity": 4
},
{
"_id": "5ff332a891aa170b60a21eaa",
"productId": 2,
"productName": "Phone",
"quantity": 5
}
],
"username": "admin",
"createdAt": "2021-01-04T15:25:35.210Z",
"updatedAt": "2021-01-04T15:25:35.210Z",
"cartId": 14,
"__v": 0
}
can you help me understand why there is duplicate entries in my db?
I'm not sure why you are using cartModel.create(ctMod,{new:true}); inorder to create a new entry to your collection.
You can simply do like this :
let ctMod = new cartModel();
ctMod.username = req.body.username;
ctMod.productsInCart = req.body.productsInCart;
try{
let insCartData = await cartModel.save();
return res.status(200).json({
error:false,
message : `New items got inserted into the cart with the ID : ${insCartData.cartId}`,
data : insCartData
});
}
catch(err){
console.error("error inserting data", err);
return res.status(500).json({error:true, message:"Something went wrong :("}).
}
UPDATE :
Reason for the duplicate entry is that you are passing the {new:true} flag in the create API. It's used to return the updated document in the findOneAndUpdate method. But in create/save, the mongoose return the data by default. Hence the flag is not needed.If you omit the flag, duplicate entries can be prevented.
Also, as you are using the create method to save the new entry, you need not create an instance of the cartModel. ie.,
let ctMod = new cartModel();
can be just
let ctMod = {}
Hope it helps!

Check if user id is in mongoose document - if yes, change property value to true?

I have an expressjs router that looks in MongoDB collection using the mongoose findById method. It returns an object where inside there exist an userHasSelected array with users id. I dont want to return users id, but just check if current users (the one who made the request) exist in the array. If he is then return true instead of returning the user id.
The verifytoken middleware in the router adds a user id property to the request.That user id is available in the get router message - can i somehow pass that to the Mongoose schema ???
//My router
router.get('/challenge/:challengeId', verifyToken ,function (req, res){
//+ Return one challenge and its options
//- Check if userId is set to options and winner properties
let userId = req.userId;
console.log(userId);
let challengeId = req.params.challengeId;
Challenge.findById(challengeId, (err, suc)=>{
if(err){
res.status(304).send(err);
}
Challenge.
res.status(200).send(suc);
});
})
// And the mongoose Schema
const mongoose = require('mongoose');
var Schema = mongoose.Schema;
//Optionsschema is use in the ChallengeSchema
var OptionSchema = new Schema({
name: { type: String},
isCorrect : { type: Boolean },
description: { type: String },
image : { type : String },
userHasSelected : { type : Object, get : returnUserChallengeStatus}
})
OptionSchema.set('toObject', { getters: true });
OptionSchema.set('toJSON', { getters: true });
var ChallengeSchema = new Schema({
shortEventId : String,
organization: String,
name: String,
winner: String,
options : [OptionSchema]
});
ChallengeSchema.set('toObject', { getters: true });
ChallengeSchema.set('toJSON', { getters: true });
ChallengeSchema.virtual('born').get(function(value) {
return this.name + "plus andet"
});
module.exports = mongoose.model('challenge', ChallengeSchema);
So again - I dont want to return the user id from the userHasSelected array - just check if he is there and if yes, use a getter or a method to set value to true.
Updated explanation
The findById returns this object / document
{
"_id":"5b86dc5bfb6fc03893e55001",
"shortEventId": "d2d3",
"organization": "Braedstrup",
"name": "1. december",
"winner": "5b864cbaa9ce291b148ddd6d",
"options": [
{
"name": "Matas",
"isCorrect": "true",
"description": "Matas er byens førende indenfor pleje og Matas er byens førende indenfor pleje og omsorg Matas er byens førende indenfor pleje og omsorg",
"image": "https://cityxpstorage.blob.core.windows.net/images/Matas.png",
"userHasSelected": [
{
"userId": "5b864cbaa9ce291b148ddd6d"
}
]
},
{
"name": "Føtex",
"isCorrect": "false",
"description": "Føtex er en dejlig butik",
"image": "https://cityxpstorage.blob.core.windows.net/images/Føtex.png"
},
{
"name": "Kvickly",
"isCorrect": "false",
"description": "Kvickly er en dejlig butik",
"image": "https://cityxpstorage.blob.core.windows.net/images/Matas.png"
},
{
"name": "MC Jørgensen",
"isCorrect": "false",
"description": "MC Jørgensen er en dejlig butik",
"image": "https://cityxpstorage.blob.core.windows.net/images/Matas.png"
}
],
"startDate": "2018-10-06T00:00:00.000Z",
"endDate": "2018-10-06T23:59:00.000Z"
}
So the nested array 'userHasSelected' contains information about the users id. I do not want to send that - instead I would like to a {userId : true}.
I have read that getters a able to handle outgoing data.
Posible Solution
I could make the check inside the router get method before returning the object to the client like this
// If user is in array set user to true. I would like to move this responsibility to the schema / document.
suc.options.forEach(option => {
if(Array.isArray(option.userHasSelected))
option.userHasSelected = {userId : true}
});
But I would really like schema to be responsible for that - Is that possible?
I had similar issue and found a workaround. Simply create an optional field on your responsible modal schema, let call it "status". On your controller, check if your array includes requested user's id and write to that field. For example;
on your schema;
caseStatus: {
type: Boolean,
required: false
},
voters: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
}],
then on your controller;
let theCase = await Case.find({speciality: res.locals.user_speciality }).exec();
let status = theCase.voters.includes(res.locals.user_id);
caseItem.caseStatus = status;

Pushing items to a nested array from an express post request

Below is the schema for my monthly budgets collection. Each of them is assigned a year and a month. Each budget has a multiple categories and sub-categories within each of them. I have decided to store them in an array.
var {mongoose} = require('../db/mongoose');
var mb = new mongoose.Schema({
month: String,
categories: [{
name: String,
sub_categories: [{
name: String,
amount: Number
}]
}]
});
var Monthly_Budgets = mongoose.model('Monthly_Budgets', mb);
module.exports = {
Monthly_Budgets
};
Below is the post express post request
app.post('/monthlyBudgets', (req, res) => {
var sub_categories = req.body.categories.sub_categories;
var categories = [{
name : req.body.categories.name,
sub_categories
}]
var budgets = new Monthly_Budgets({
month : req.body.month,
year: req.body.year
})
budgets.categories = categories;
budgets.save().then((docs) => {
res.send(docs);
console.log(docs);
}).catch((e) => res.status(404).send(e));
})
When I send the post request from postman, it does not result in an error, but it gives this:
{
"_id" : ObjectId("5b8a3280924c2d0dea15a1df"),
"month" : "September",
"year" : 2018,
"categories" : [
{
"_id" : ObjectId("5b8a3280924c2d0dea15a1e0"),
"sub_categories" : []
}
],
"__v" : 0
}
I can't seem to figure out the issue. Please help.

Append/Add Objects without creating a new Parent in Mongoose

My Schema be like as follows
var DeptSchema = new Schema({
name : {type : String, default: ''},
sku : {type : String, default: ''}, // (SKU = stock keeping unit)
Product : {
name : {type : String, default: '', unique:true},
sku : {type : String, default: '', unique:true}, // (SKU = stock keeping unit)
description : {type : String, default: '100gm'},
price : {type : String, default: ''},
quantity : {type : Number, default: '0'},
isFav : {type : Boolean, default: 'false'}
}
});
Via Mongoose I've Created an API, but PROBLEM starts when I want to add Products to a specific Dept(Department), A whole new Instance of Department is created instead of the new Product getting appended to the existing Department.
My POST/PUT stated below is
.put(function(req, res) {
// use our Dept model to find the Dept we want
Dept.findById(req.params.Dept_id, function(err, Dept) {
if (err)
res.send(err);
Dept.name = req.body.name; // update the Dept info
Dept.sku = req.body.sku;
Dept.Product.name = req.body.ProductName;
Dept.Product.sku = req.body.ProductSKU;
Dept.Product.description = req.body.ProductDescription;
Dept.Product.price = req.body.ProductPrice;
Dept.Product.quantity = req.body.ProductQuantity;
Dept.Product.isFav = req.body.ProductisFav;
// save the Dept
Dept.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Department updated!' });
});
});
})
.post(function(req, res) {
var dept = new Dept(); // create a new instance of the Dept model
dept.name = req.body.name; // set the Dept name (comes from the request)
dept.sku = req.body.sku;
dept.Product.name = req.body.ProductName;
dept.Product.sku = req.body.ProductSKU;
dept.Product.description = req.body.ProductDescription;
dept.Product.price = req.body.ProductPrice;
dept.Product.quality = req.body.ProductQuality;
dept.Product.isFav = req.body.ProductisFav;
// save the Dept and check for errors
dept.save(function(err) {
if (err)
res.send(err);
res.json({ message: 'Department created!' });
});
})
e.g. We can easily see from the output that Different Fruits instead of appending to the same Fruits Dept. are creating a whole another instance. Also why does ProductSchema not have auto generated Object Id?
[
{
"__v": 0,
"_id": "5528027cd4eb13d80cf81f87",
"Product":
{
"isFav": true,
"quantity": 34,
"price": "128",
"description": "1kg",
"sku": "APL",
"name": "Apple"
},
"sku": "FRT",
"name": "Fruits"
},
{
"_id": "552824abd67bf9d81391ad92",
"__v": 0,
"Product":
{
"isFav": true,
"quantity": 0,
"price": "40",
"description": "1kg",
"sku": "ORG",
"name": "Orange"
},
"sku": "FRT",
"name": "Fruits"
}
]
Thank You for being Patient.
You have declared Product to be an object and not an array.
Product: {...} --> Product: [{...}]
Also you would need to update your put method to push a new item onto the Dept.Product array rather than updating the properties of Dept. You can read how to properly use subdocs in the documentation.

Querying Embedded Documents with NodeJS and Mongoose

I need to query the following data from mongodb:
Project has many Regions, a Region has many Links
Here's the data:
{ "_id" : ObjectId( "4f26a74f9416090000000003" ),
"description" : "A Test Project",
"regions" : [
{ "title" : "North America",
"_id" : ObjectId( "4f26a74f9416090000000004" ),
"links" : [
{ "title" : "A Really Cool Link" } ] },
{ "description" : "That Asia Place",
"title" : "Asia",
"_id" : ObjectId( "4f26b7283378ab0000000003" ),
"links" : [] },
{ "description" : "That South America Place",
"title" : "South America",
"_id" : ObjectId( "4f26e46b53f2f90000000003" ),
"links" : [] },
{ "description" : "That Australia Place",
"title" : "Australia",
"_id" : ObjectId( "4f26ea504fb2210000000003" ),
"links" : [] } ],
"title" : "Test" }
Here's my model setup:
// mongoose
var Link = new Schema({
title : String
, href : String
, description : String
});
var Region = new Schema({
title : String
, description : String
, links : [Link]
});
var Project = new Schema({
title : String
, description : String
, regions : [Region]
});
mongoose.model('Link', Link);
mongoose.model('Region', Region);
mongoose.model('Project', Project);
var Link = mongoose.model('Link');
var Region = mongoose.model('Region');
var Project = mongoose.model('Project');
The query that I'm struggling with is returning a single region object matched on ID. I'm fine returning all of the regions with all of their respective links, but limiting it to the ID of just one region has been a problem.
Here's my broken route that I'm working with:
app.get('/projects/:id/regions/:region_id', function(req, res){
Project.findById(req.param('id'), function(err, project) {
if (!err) {
project.regions.findById(req.params.region_id, function(err, region) {
if (!err) {
res.render('project_regions', {
locals: {
title: project.title,
project: project
}
});
}
});
} else {
res.redirect('/')
}
});
});
I know the above will not work because the object returned by "findById" does not respond to findByID, but it illustrates what I'm trying to do. I tried doing a custom query, but it always returned the entire document, not the single Region that I wanted.
I also tried querying directly off the Region Schema, but that is not returning any results. I assume to query from a Schema that's a subdocument I would have to contextually provide what the parent document is.
In other words, the following does not return an "region" object with data:
app.get('/projects/:id/regions/:region_id', function(req, res){
Region.findById(req.params.region_id, function(err, region) {
if (!err) {
res.render('project_regions_show', {
locals: {
title: "test",
region: region
}
});
} else {
res.redirect('/')
}
});
});
Appreciate any help.
bstar,
You can prevent sub documents from loading by using the query.select:
ie.
Entity.find({ ... })
.select({childentities: 0}) //excludes childentities
.exec(function(err, entities){
...
res.json({entities: entities});
});
I'd recommend extracting the regions into a separate collection with a "projectId: ObjectId()" field. That may give you the querying flexibility you're looking for.

Resources