I am new to mongoose and currently I am coding an app to learn it. I have an Artist Schema and a form that search with multiple and optional fields. For instance the user can put a name, a minimum age and search through the model without filling the other form fields.
On the controller I take all the fields from the form through the req.body object and I have a query object which with multiple if statements I check whether or not the property is not undefined, and if not I put it in the object as a property and the object is passed to the find method.
The problem is when I fill the min age and put to the query object this property query.min_age = { $gte: min_age} the $gte converts into a string and as a result can't run the find method properly.
The Mongoose Model
const mongoose = require("mongoose");
const AlbumSchema = require("./album")
const CompanySchema = require("./company")
const Schema = mongoose.Schema;
const ArtistSchema = new Schema({
name: String,
age: Number,
yearsActive: Number,
albums: [AlbumSchema],
company:[{type: Schema.Types.ObjectId, ref: "Company"}]
});
const Artist = mongoose.model("artist", ArtistSchema);
module.exports = Artist;
The Controller
app.post("/", (req, res, next) => {
const name = req.body.name;
const min_age = req.body.min_age;
const min_active = req.body.min_active;
const sort = req.body.sort;
const query = {};
if(name) {
query.name = name;
}
if(min_age) {
query.min_age = { $gte: min_age};
}
if(min_active) {
qyery.min_active = {gte: min_active};
}
Artist.find(query).
sort(sort)
.then( artists => {
res.render("index", {
artists: artists
})
});
});
The image below is depicting the string $gte when I console it:
Keys in JS objects are always casted to a string, so there is nothing wrong with $gte being a string on a screenshot that you've posted.
As for the min_age value, in your code I cannot see anything that would convert it to a string and mongoose by itself is not doing it as well.
It looks like the problem is in a test request. Please check if you are sending min_age as a number in a POST request, or as a string. It should be a number or else you need to convert it to a number in your controller (with parseInt() for example)
const name = new RegExp(req.body.name, 'i');
const min_age = req.body.min_age;
const min_active = req.body.min_active;
const sort = req.body.sort;
const query = {};
if(req.body.name!=undefined && req.body.name!=''){
query["$and"]=[{name :re}]
}
if(min_age){
query["$and"].push({min_age:{ $gte: parseInt(min_age)}})
}
if(min_active ){
query["$and"].push({min_active:{ $gte: parseInt(min_active)}})
}
let artists=await Artist.find(query).sort(sort)
res.render("index", {
artists: artists
})
Related
I am practicing my express.js skills by building a relational API and am struggling to populate keys in a schema.
I am building it so I have a list of properties, and those properties have units. The units have a propertyId key.
This is currently returning an empty array, whereas if i remove the populate({}) it returns an array of ObjectIds.
I've read a number of posts and some people solved this by using .populate({path: 'path', model: Model}); but this doesn't seem to be doing the trick. I think it might be the way I am adding a propertyId to the unit but I'm not sure. Can anyone see where I am going wrong? Any help will be massively appreciated.
Here are the schemas.
Property:
const mongoose = require('mongoose');
const { Schema } = mongoose;
const PropertySchema = new Schema({
title: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
units: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'unit'
}
]
});
module.exports = Property = mongoose.model('property', PropertySchema);
Unit:
const mongoose = require('mongoose');
const { Schema } = mongoose;
const UnitSchema = new Schema({
title: {
type: String,
required: true
},
propertyId: {
type: Schema.Types.ObjectId,
ref: 'property'
}
});
module.exports = Unit = mongoose.model('unit', UnitSchema);
I am then creating the unit like this:
-- api/properties/:id/units --
router.post('/:id/units', async (req, res) => {
// Get fields from req.body
const { title } = req.body;
// Get current property
const property = await Property.findById(req.params.id);
try {
// Throw error if no property
if (!property) {
return res.status(400).json({ msg: 'Property not found' });
}
// Create new unit
const newUnit = new Unit({
title,
propertyId: req.params.id
});
// Add new unit to property's units array
property.units.unshift(newUnit);
// Save property
await property.save();
// Return successful response
return res.status(200).json(property);
} catch (error) {
console.error(error.message);
return res.status(500).send('Server error');
}
});
And trying to populate in the GET request
-- /api/properties/:id/units --
const Unit = require('../../models/Unit');
router.get('/:id/units', async (req, res) => {
const property = await Property.findOne({ _id: req.params.id }).populate({path: 'units', model: Unit});
const propertyUnits = property.units;
return res.status(200).json(propertyUnits);
});
If i remove the .populate({path: 'units', model: Unit});, I get a list of unit id's like this:
[
"5ff7256cda2f5bfc1d2b9108",
"5ff72507acf9b6fb89f0fa4e",
"5ff724e41393c7fb5a667dc8",
"5ff721f35c73daf6d0cb5eff",
"5ff721eb5c73daf6d0cb5efe",
"5ff7215332d302f5ffa67413"
]
I don't know, why you don't try it like this:
await Property.findOne({ _id: req.params.id }).populate('units')
I've been try that code above and it's working.
Note: Make sure to check your req.params.id is not null or undefined and make sure the data you find is not empty in your mongodb.
Updated: I've been try your code and it's working fine.
The issue was caused by inconsistent naming and not saving the new created unit as well as the updated property.
I double checked all my schema exports and references and noticed I was using UpperCase in some instances and LowerCase in others, and saved the newUnit as well as the updated property in the POST request and it worked.
I want to save complex data, ie array of objects to mongoose. I have tried few things but i couldn't save the data.
I defined my schema as above and i want to save array of objects that could have any level of nesting.
Schema
const mongoose = require('mongoose);
const PostSchema = new mongoose.Schema({
post: [{}]
});
let PostModel = mongoose.Model('Post', PostSchema)
The Data:
Here is the code I used to save the data
app.post('/saveData, async (req, res) => {
const response = await Post.create(req.body);
res.json({
data: response
});
});
app.listen(8008, () => {
console.log('server running);
});
The problem is that i cant retrieve the data. it returns array of objects equal to the number of saved array but with no data in it.
How can it be done?
This code works for me.
const PostModel = require('./Post'); //declare your model
app.post('/saveData', async (req, res) => {
const objModel = new PostModel();
objModel.post = req.body; //assign the data post array.
const response = await objModel.save();
res.json({
data: response
});
});
Your post schema looks weird.
You have a collection for Posts and then within a posts schema, you have a posts array.
What is the point of that?
The post collection already is an "array" for posts.
// Perhaps you are looking for something like this.
const PostSchema = new mongoose.Schema({
title: String,
content: String,
level: Number,
footer: String,
author: ObjectId,// who wrote the post
comments: [{
user: ObjectId,
comment: String
}],
... createdAt, updatedAt etc
});
Your data structure doesnt seem to match your schema either.
e.g
await Post.create({posts: req.body});
im trying to update values in the collection, only way i got it to work is updating whole values together, if i update one value the rest of values gets null if exclude updating the file (image) i get error.
1)upading whole values sucess
1-2))mongoose
2) when image excluded "Cannot read property 'originalname' of undefined"
3)when updating image only rest of values converted to null
model
const mongoose = require ("mongoose");
const user = require('../models/usermodel')
const Schema = mongoose.Schema;
const sellerSchema= mongoose.model('seller',new mongoose.Schema({
image:{type:String,
required:true}
password:{type:String,
required:true}
lastName:{type:String,
required:true}
name:{type:String,
required:true}
numTel:{type:Number,
required:true}
email:{type:String,
required:true}
}))
module.exports = sellerSchema;
control
const sellermodel = require('../models/sellermodel');
var fs = require('fs');
const multer = require('multer');
const upload = multer({dest:__dirname +'/uploads/images'});
module.exports = {sellerupdate: function (req, res) {
sellermodel.updateOne({_id: req.params.id},
{$set:{name:req.body.name,
lastName:req.body.lastName,
email:req.body.email,
password:req.body.password,
image:req.file.originalname,}},
function (err,data) {
if (err) {res.json({ msg: 'product not found'+err })
}
else {
res.json({ msg: 'product updated successfully'+data} )
}
})
}
}
router
const sellercontrols = require("../controllers/sellercontrols");
const router = require("express").Router();
const multer = require('multer');
const upload = multer({dest:__dirname + '/uploads/images/'});
router.put('/update/:id',upload.single('image'),sellercontrols.sellerupdate);
module.exports = router;
server
const express = require('express');
const app = express();
const bodyParser = require("body-parser");
const sellerrouter= require('./routers/sellerrouter');
const db = require('./models/db');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({
extended: false }));
app.use("/seller",sellerrouter);
app.listen(8080,function(){
console.log('server is running')})
please help
//This is the answer you've been looking for, I spent an hour trying to figure it. use this first syntax if email and password is inside an object else use the shorter one
const password = "new password";
const email = "new email";
let result = collection find({})
result = result.objectName
// now update email and password in the result object like this
result.email = email;
result.password = password
//after that send it back to the database
collection.updateOne({}, {$set: {fieldName: result}})
//and that's it or use this shorter one if its not in an object
collection.findOneAndUpdate({}, {$set: {email, password}})
if the email and password is inside an object use this
This is easier said being done as you are using $set and with that if you don't pass a value, it will default to null and in turn making your values in a document null. While you want to update multiple fields (could be 1 or 2 or whole document), you need to have a fail-save system so that for not changed values you keep the original ones.
This is how I would approach to this issue
1) Find the Document I want to update(sellermodel.FindOne)
2) Compare the Object Returned with passed Object, Updating the ones changed and keeping original if not changed (Hence solving null issues)
3) Update the Object with new values using (sellermodel.updateOne)
I'm assuming this would work but I didn't tested it, so you might have to change little bit here and there
sellermodel.FindOne({_id: req.params.id},(err,item)=>{
if(err) console.log(err);
else{
/* This will create a object with req.body and req.file (Assuming both are in JSON)*/
let mergedObject= {...req.body,...req.file}
/* This will Compare Object from Mongoose and the object you passed and will update the values you passed
and keeping the orignal that were not changed*/
let ObjectToUpdate=updateObjectValue(item,mergedObject)
/* Delete ObjectID otherwise it will throw an error */
delete ObjectToUpdate._id
delete ObjectToUpdate.__v
/* After You have a perfect Object You can update the values */
sellermodel.updateOne({_id: req.params.id},ObjectToUpdate,(err,data)=>{
if(err) {res.json({msg:'error occured'})}
else{
res.json({ msg: 'product updated successfully'+data})
}
})
}
})
var updateObjectValue = (obj1, obj2) => {
var destination = Object.assign({}, obj2);
Object.keys(obj1).forEach(k => {
if(k in destination) {
destination[k] = obj1[k];
}
});
return destination;
}
In your query, you are fetching all the values from req.body and passing it to $set, so I think when you don't send a value for a particular field it defaults to null and hence all values are updated to null.
If you don't want other values to change to null, you must fetch the previous values from the collection, populate in your form, so it will be accessible in req.bodywhen you submit. In postman you must pass the existing values along with the ones you want to update OR don't pass the value to $set if it's null.
If you don't want to do either of the 2, then another way would be :
sellermodel.findById({
_id: req.params.id
}, function (err, docs) {
if(err){ do something }
else{
let name = req.body.name,
lastName = req.body.lastName,
email = req.body.email,
password = req.body.password;
if(name == null || name == ''){
name = docs.name;
}
if(lastName == null || lastName == ''){
lastName = docs.lastName;
}
if(email == null || email == ''){
email = docs.email ;
}
if(password == null || password == ''){
password = docs.password;
}
//then your options will look like
let options;
if(req.file != undefined){
options = {
name: name,
lastName: lastName,
email: email,
password: password,
image: req.file.originalname
}
} else {
//Not passing the image here will not change the existing value in your document.
options = {
name: name,
lastName: lastName,
email: email,
password: password
}
}
//your update query
sellermodel.updateOne({_id: req.params.id},{$set:options},
function (err,data) {
if (err) {
res.json({ msg: 'product not found'+err })
} else {
res.json({ msg: 'product updated successfully'+data} )
}
})
}
})
In case of req.file.originalname, it will obviously throw the error you showed in one of your screenshots as req.file is not defined because you are not passing anything. I suggest you define your options to update the way. I have shown above.
I want to rename the collection in my schema, but mongodb keeps using the old name.
I have been running my code with the schema name set to '__filters', but now I need to change the name to '__filter'. ( NOT plural )
When I create a filter, mongodb creates a '__filters' collection
This is how I had the original Schema set up, note plural 'filters'
// create the schema
const FiltersSchema = new Schema({
test: {type: String, required: true},
})
module.exports = Filters = mongoose.model('__filters', FiltersSchema)
Now I want to make the name of the collection singular '__filter'. This is the new schema that I want to use: NOTE: ALL singular now
// create the schema
const FilterSchema = new Schema({
test: {type: String, required: true},
})
module.exports = Filter = mongoose.model('__filter', FilterSchema)
Here is the code that I am using:
const Filter = require('./Filter');
createFilter = ( test ) => {
return new Promise((resolve, reject) =>{
var errors = {};
Filter.findOne( {test:test} )
.then(found => {
if(found) {
errors.err = {'inUse':'already created'};
console.log(errors);
reject(errors);
} else {
const newFilter = new Filter({
test: test
});
newFilter.save()
.then(f => {
if(debugThis){
console.log(' ');
console.log(' created ' + JSON.stringify(f));
console.log(' ');
}
resolve(f);
})
.catch(err => {
errors.exception = {'save':err};
console.log(errors);
reject(errors);
});
}
})
.catch(err => {
errors.exception = {'findOne':err};
reject(errors);
})
});
};
it's almost like there is some cache somewhere that is keeping the older 'filters' schema around.
Is there something I need to clear?
I have even tried this, which didn't work either
let dbc = mongoose.connection.db;
dbc.collection('filters').rename('filter')
.then()
.catch(err =>{});
I closed DevStudio and restarted it.
I have created a new database in MongoDB
I restarted the MongoDB server service
Nothing seems to reset '__filters' to '__filter'
In desperation, I remove the _filter from the schema and it crashed and spit this out:
TypeError: Cannot read property 'toLowerCase' of undefined
at pluralize
(\node_modules\mongoose-legacy-pluralize\index.js:85:13)
Does mongoose make names plural automatically ??
Well blow me down olive oil... mongoose makes stuff plural... how 'nice' of them to do that... I DON'T want plural names...
OMG... I can't believe this... 4 hours of my time wasted on this stupid stuff:
Why does mongoose always add an s to the end of my collection name
I am writing post api using restify and mongodb with mongoose.
'use strict'
const Trending = require('../models/trending');
const trendingController = {
postTrending: (req, res, next)=>{
let data = req.body || {};
console.log(Trending);
let Trending = new Trending(data);
Trending.save(function(err) {
if (err) {
console.log(err)
return next(new errors.InternalError(err.message))
next()
}
res.send(201)
next()
})
}
}
here error is that Trending is not defined, I don't know why.. other similar controllers are working fine.
Trending is mongoose Model
model code
'use strict'
const mongoose = require('mongoose');
const timestamps = require('mongoose-timestamp');
const Schema = mongoose.Schema;
const TrendingSchema = new mongoose.Schema({
_id: Schema.Types.ObjectId,
headline: {
type: String,
required: true
},
description: String,
data: [
{
heading: String,
list: [String]
}
],
tags: [{ type: Schema.Types.ObjectId, ref: 'Tags' }]
});
TrendingSchema.plugin(timestamps);
const Trending = mongoose.model('Trending', TrendingSchema)
module.exports = Trending;
folder structure
controllers
--- trending.js
models
---trending.js
You are having this problem because of this line;
let Trending = new Trending(data);
You should avoid using the same variable name for two different things to prevent this kind of problem. Especially in this case where you are using uppercase letter for an object when you should use it only for classes.
Replace that line with;
let trending = new Trending(data);
The problem happens because let (and const) are block scoped so a variable with the same name but from an outer scope will be overridden. You then get undefined for this variable because you are referencing it in the same line you are declaring it, so it is in fact still undefined.