I'm new to the async/await world and trying to experiment a bit with Mongoose + MongoDB + Node.JS
I have this piece of code
exports.updateBrandPreferences = async (req,res) => {
var userID = req.body.playerID;
var newBrands = req.body.brandList;
console.log("Ricevuto la seguente lista: " + newBrands);
async.each(newBrands, function(brand,error) {
Brand.findOneAndUpdate({'name': brand},{$addToSet: {peopleInterested: userID}}, {new:true}).exec().then((results) => {
console.log(results);
User.findOneAndUpdate({_id: userId},{$addToSet: {interestedBrands: results._id}}, {new:true}).exec().then((risultato)=> {
console.log(risultato);
return risultato;
}).catch((err) => {
return "error";
});
return results;
}).catch((err) => {
return "error";
});
});
return res.status(200).json({"message": "OK"});
};
Taking some elements from the request, my objective is to associate the specified user with a list of some brands of interest. While the first query works (so Brands do now have new users inside them to symbolize their interest), this doesn't work for users as the second query doesn't get executed.
What am I missing? Schemas are the following:
Brand :
var BrandSchema = new Schema({
name: {type: String, required: true, unique: true},
peopleInterested: Array,
}, {
collection: 'brands',
retainKeyOrder: true,
timestamps: true,
}).plugin(mongoosePaginate);
User:
var UserSchema = new Schema({
isAdmin: {type: Boolean, default: false},
name: String,
surname: String,
email: { type: String, lowercase: true, required: true, trim: true, unique: true, dropDubs: true },
password: { type: String, required: true },
salt: { type: String },
verified: { type: Boolean, default: false },
bio: {
type: { type: String, enum: [0,1] }, // 0='Squadra', 1='Giocatore'
birthday: String,
height: Number,
number: Number,
role: { type: String, enum: [0,1,2,3] }, // 0='Playmaker', 1='Ala', 2='Guardia', 3='Centro'
team: String,
city: String,
fiscalCode: {type: String, maxlength:16}
},
newsletter: {type: Boolean, default: false},
lastCheckin: {type: mongoose.Schema.Types.ObjectId, ref: 'Checkin'},
follows: [{type: mongoose.Schema.Types.ObjectId, ref: 'Structure'}],
interestedBrands: Array,
score: { type: Number, default: 0 },
profilePicture: String,
lastLogin: {type: Date},
facebook: {
id: String,
accessToken: String,
profileImage : String
}
}, {
collection: 'users',
retainKeyOrder: true,
timestamps: true,
}).plugin(mongoosePaginate);
This is the same problem as this one or this one. async function, async library and plain promises are mixed together. Promises aren't chained correctly inside callbacks.
async function (it is syntactic sugar for promises) and async library (async.each) solve similar tasks, they shouldn't be mixed unless proven otherwise. async library was a predecessor to native promises, it is callback-based and may result in callback hell; something that promises are supposed to help with (they are callback-based but provide a useful pattern for chaining).
Promises inside async function are supposed to be processed in series with await and loop statement (for..of), in parallel with await and Promise.all.
Express is unaware of promises. As explained in this answer, all rejections inside middleware should be handled.
It should be:
exports.updateBrandPreferences = async (req, res, next) => {
try {
var userID = req.body.playerID;
var newBrands = req.body.brandList;
await Promise.all(newBrands.map(async (brand) => {
await Brand.findOneAndUpdate({'name': brand},{$addToSet: {peopleInterested: userID}}, {new:true});
await User.findOneAndUpdate({_id: userId},{$addToSet: {interestedBrands: results._id}}, {new:true});
}));
return res.status(200).json({"message": "OK"});
} catch (err) {
next(err);
}
};
Related
I have found a few similar questions on stack overflow like this one:
How to save array of Strings in Node Mongodb
Mongoose - Save array of strings
but I cant figure out why my method is not working
I am trying to save the string of arrays "jobType".
context: I am creating an app where people can post jobs.
each job can have multiple types.
here is my job model::
const mongoose = require("mongoose");
const postSchema = mongoose.Schema({
content: { type: String, required: true },
imagePath: { type: String, required: true },
state: { type: String, required: true },
substate: { type: String, required: true },
address: { type: String, required: true },
jobType: { type: [String] },
creator: { type: mongoose.Schema.Types.ObjectId, ref: "User", required: true }
});
module.exports = mongoose.model("Job", postSchema);
this is the API used to save the data on MongoDB:
I am 100% sure that the data is getting to the API correctly.
the parameter "req.body.jobtype" contains all the info as a string.
I am trying to use JSON.parse to change the string into an array but its not working.
when I check MongoDB, an empty array is being stored
const Job = require("../models/job");
exports.createJob = (req, res, next) => {
console.log('create job api hit')
const url = req.protocol + "://" + req.get("host");
const post = new Job({
content: req.body.content,
imagePath: url + "/images/" + req.file.filename,
creator: req.userData.userId,
state: 'new',
substate: 'new',
address: req.body.address,
jobtype: JSON.parse(req.body.jobtype) // fix: not storing correctly
});
post
.save()
.then(createdJob => {
res.status(201).json({
message: "Job added successfully",
post: {
...createdJob,
'pecker':'pecker hecks out',
id: createdJob._id
}
});
})
.catch(error => {
res.status(500).json({
message: JSON.stringify(error)
});
});
};
You have a typo. In your model, you defined jobType property, but when saving the data, you are passing it as jobtype.
So, instead of this:
jobtype: JSON.parse(req.body.jobtype)
Do this:
jobType: JSON.parse(req.body.jobtype)
connection api
import mongoose from "mongoose";
const MONGODB_URL = NEXT_MONGO_URL
if (!MONGODB_URL) {
throw new Error(error);
}
let cached = global.mongoose;
if (!cached) {
cached = global.mongoose = {
conn: null,
promise: null,
};
}
async function dbConnect() {
if (cached.conn) {
return cached.conn;
}
if (!cached.promise) {
const opts = {
bufferCommands: false,
};
cached.promise = mongoose.connect(MONGODB_URL, opts).then((mongoose) => {
return mongoose;
});
}
cached.conn = await cached.promise;
return cached.conn;
}
export default dbConnect;
user api
__________________________________________________________________________
var mongoose = require("mongoose");
const userSchema = new mongoose.Schema(
{
firstName: { type: String, required: true, maxlength: 256 },
lastName: { type: String, required: true, maxlength: 256 },
username: {
type: String,
lowercase: true,
unique: true,
required: [true, "can't be blank"],
match: [/^[a-zA-Z0-9]+$/, "is invalid"],
index: true,
},
password: { type: String, required: true, maxlength: 256 },
age: { type: Number },
email: {
type: String,
lowercase: true,
unique: true,
required: [true, "can't be blank"],
match: [/\S+#\S+\.\S+/, "is invalid"],
index: true,
},
},
{ timestamp: true }
);
const Users = mongoose.models.users || mongoose.model("users", userSchema);
export default Users;
I tried deleting and recreating the user part of the db and even rewriting the schema.
I currently have the same code working for my blog posts part of the db, so I don't think its the connection api
any help would be appreciated, please explain it like you would to a 10 year old
Ive been learning to code for a few month. thank you
Try and understand what the error message is saying: you're trying to access a property users on something that's undefined.
So that suggests that mongoose.models is undefined, because that's where you're trying to access that users property.
Since mongoose.models isn't even documented, I would suggest not using it and just use documented methods of retrieving a model:
const Users = mongoose.model("users", userSchema);
I am new to programming and trying my best but without any help I don't think I am able to find it ;)
I want to display all products from mongoDB by brand. I already have route for category but in the same time when I am trying to implement the same strategy to brands I have 404 not found.
my code :
router.get('/:categorySlug', (req, res, next) => {
let filter = {};
if(req.query.hasOwnProperty("filter")){
filter['price'] = req.query.price
}
const slug = req.params.categorySlug;
Category.findOne({slug: slug})
.select('_id')
.exec()
.then(category => {
if(category){
if(category.parent === ""){
Product.find({category: category._id})
.select('_id name price productPic category brand slug')
.sort(filter)
.exec()
.then(products => {
res.status(200).json({
message: products
})
})
.catch(error => {
return res.status(404).json({
message: error
})
})
}
}else{
return res.status(404).json({
message: 'Not Found'
})
}
})
.catch(er => {
res.status(500).json({
error: er
});
});
router.get('/:brandSlug', (req, res, next) => {
const slug = req.params.brandSlug;
Brand.findOne({slug: slug})
.select('_id parent')
.exec()
.then(brand => {
if(brand){
Product.find({brand: brand._id})
.select('_id name price productPic brand slug')
.exec()
.then(products => {
res.status(200).json({
message: products
})
})
.catch(error => {
return res.status(404).json({
message: error
})
})
}else{
return res.status(404).json({
message: 'Not Found'
})
}
})
.catch(er => {
res.status(500).json({
error: er
});
});
Category, brand and product schema :
const brandSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
slug: { type: String, required: true, unique: true },
const categorySchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
slug: { type: String, unique: true },
const productSchema = mongoose.Schema({
_id: mongoose.Schema.Types.ObjectId,
name: { type: String, required: true },
slug: { type: String, required: true, unique: true },
price: { type: Number, required: true },
oldPrice: { type: Number, default: 0 },
stock: { type: Number, required: true },
description: { type: String },
productPic: [
{
img: String
}
],
keyword: {type: String},
category: { type: mongoose.Schema.Types.ObjectId, ref: 'Category', required: true },
brand: { type: mongoose.Schema.Types.ObjectId, ref: 'Brand', required: true }
You don't need to do two DB calls to get that required data, Instead you can join those two collections & get required data in one DB call ( Try to read about MongoDB's native $lookup or Mongoose's .populate() - which is a kind of wrapper to $lookup ).
But for now you can replace this :
Product.find({brand: brand._id})
with :
Product.find({brand: mongoose.Types.ObjectId(brand._id)})
Actual issue is brand._id is a string in code (In general when you read docs of brand collection in MongoDB & then log/print it or even use it in code You can see ObjectId()'s are converted to string's as ObjectId() is from BSON & not supported by JSON). So you need to convert input (brand._id) from string to ObjectId() before firing a query to product collection as brand field in product collection is of type ObjectId() !!
Note : Do not forget to import mongoose in this file otherwise it would fail that given point.
var questionSchema = new Schema({
category: { name:
{
type: String,
lowercase: true,
required: [true, 'Category is a required field']
},
question:[
{
q: {
type: String,
lowercase: true
},
options: {
option1:{
type: String,
lowercase: true
},
option2:{
type: String,
lowercase: true
]
},
option3:{
type: String,
lowercase: true
},
option4:{
type: String,
lowercase: true
},
},
ans: {
type: String,
lowercase: true
},
level:{
type: String,
lowercase: true
}
}
]
},
},
{
strict: true,
runSettersOnQuery: true,
timestamps: {
createdAt: 'created', updatedAt: 'updated'
}
});
This is my question schema. I am trying to get all the questions of a particular category. Till now I have tried this ---
function (req,res,next) {
console.log(req.params.qcategory);
Question.find({
category:{
name: req.params.qcategory,
}
},'', function (err,data) {
if (err) {
err.status = 406;
return next(err);
}
console.log(data);
return res.status(201).json({
message: ' success.',data:data
})
})
};
In req.params.qcategory it contains the name of the category like 'programming' for instance.
But it returned me an empty array. **PS. There are questions of programming category in my DB. Then What am i doing wrong ???
Also is there a way to first check what category a question is and then add to the question array inside that particular category. I don't want to add questions with categories again and again instead i want to check if that category already exists in the database and push the question inside the array of that question category. For instance if a question is of 'programming' category and in the database there is already a question which is of again a 'programming' category then push the question inside the array of programming category.
If my questions are not clear then please comment I will respond quickly.
Try this, Not tested
function (req,res,next) {
console.log(req.params.qcategory);
Question.find({
"category.name": req.params.qcategory,}
, function (err,data) {
if (err) {
err.status = 406;
return next(err);
}
console.log(data);
return res.status(201).json({
message: ' success.',data:data
})
})
};
Replace the
{category:{name:req.params.qcategory}} with {"category.name":req.params.qcategory}.
read more on mongodb's Query Embedded Documents https://docs.mongodb.com/.../method/db.collection.find/
Try this.
function (req,res,next) {
console.log(req.params.qcategory);
const name = req.params.qcategory;
Question.find({name})
.then((data)=>return res.status(201).json({message:"success",data:data})
.catch(err=> {
res.status(406)
return next(err);
})
};
I am trying to save template based on user id , How can i make sure when template save it save with user id _id ? i added reference to the templateSchema for User.
user.model.js
var UserSchema = new mongoose.Schema({
_id: { type: String, required: true, index: {unique: true}},
firstName: String,
lastName: String,
type: String,
groups:[{type: String, ref: 'Group', required: false}]
},
{
toObject: {
virtuals: true
},
toJSON: {
virtuals: true
}
});
export default mongoose.model('User', UserSchema);
template.model.js
var User = require('../user/user.model.js');
var TemplateSchema = new mongoose.Schema({
_id: { type: String, required: true},
name: String,
id: String,
appliesTo: [],
properties: [],
createdBy: { type: mongoose.Schema.Types.ObjectId, ref: 'User'}
});
export default mongoose.model('Templates', TemplateSchema);
template.controller.js
var eTemplate = require('./template.model');
export function create(req, res) {
console.log(req.body);
eTemplate.createAsync(req.body)
.then(responseWithResult(res, 201))
.catch(handleError(res));
}
Mongoose has two built-in functions that are called before (pre) and after (post) you save a document. My advice is to make use of them. Here is an example of my code in which I search for an sequence number before saving the user document. You can do the same: When you save the template, make a request for the user id to the database (Or vice-versa). You can even save one, get the id and save the other.
Bellow follows my code for the sequence and the user.
var UserSchema = new Schema({
username: { type: String, required: true, unique: true },
id: { type: String },
...
});
UserSchema.pre('save', function(next) {
let doc = this;
let id = 'userSeq'
Sequence.findByIdAndUpdate(id, { $inc : {nextSId : 1} }, function(error,data) {
if(error)
next(error)
doc.id = data.nextSId-1;
next();
})
});
I hope my answer was useful for you. Just a remark, pre and post are not called in the event of updates for the document.