Mongoose .populate() not working correctly - node.js

I'm trying to create a Tumblr clone with GraphQL and MERN. Right now I'm just trying to create the template for posts with photos.
Just in case it's relevant, I am doing regular REST post requests with axios and express for the image files. I take the response from those, map over the _ids and send them in the createPost mutation.
In graphiql I can query for a single Image model and get everything back fine, like so:
{
image(_id: "someId"){
_id
url
created
}
}
But when I do a subquery with the ObjectIds I've pushed into the Post arrays I get null for everything besides _id and __typename:
{
post(_id: "someId"){
_id
mainImages {
_id //returns value
url //returns null
created //returns null
__typename //returns value
}
}
}
The posts have two arrays of objects with ObjectId and ref for images, the Post model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = new Schema({
mainImages: [
{
type: Schema.Types.ObjectId,
ref: 'Image'
}
],
bodyImages: [
{
type: Schema.Types.ObjectId,
ref: 'Image'
}
],
})
module.exports = mongoose.model('Post', PostSchema, 'posts')
The Image model:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const ImageSchema = new Schema({
url: {
type: String,
required: true
},
created: {
type: Date,
required: true
}
})
module.exports = Image = mongoose.model('Image', ImageSchema, 'images');
The PostType:
const PostType = new GraphQLObjectType({
name: 'PostType',
fields: () => ({
_id: { type: GraphQLID },
mainImages: {
type: new GraphQLList(ImageType),
resolve(parentValue) {
return Post.findById(parentValue._id)
.populate('images')
.then(post => post.mainImages)
}
},
bodyImages: {
type: new GraphQLList(ImageType),
resolve(parentValue) {
return Post.findById(parentValue._id)
.populate('images')
.then(post => post.bodyImages)
}
},
})
})
module.exports = PostType;
I'm wondering if .populate('images') isn't working correctly. I thought that if you have the ObjectIds then .populate() can take care of the rest. I've been looking around at a bunch of different questions but none of them have seemed to be relevant enough to my situation, and the GraphQL and Mongoose docs also haven't given me a breakthrough yet.

Alright so in the latest edition of I wasn't reading the docs correctly, I finally figured out what I was doing wrong.
.populate() takes a string that denotes a path to the field within the document that you're trying to populate the field of.
I figured out the solution while I was reading about populating multiple fields in a single document.
For some reason I thought the 'path' was supposed to point to the name of the collection in the database:
const PostType = new GraphQLObjectType({
name: 'PostType',
fields: () => ({
_id: { type: GraphQLID },
mainImages: {
type: new GraphQLList(ImageType),
resolve(parentValue) {
return Post.findById(parentValue._id)
.populate('images') //incorrectly pointing towards the collection in db
.then(post => post.mainImages)
}
},
bodyImages: {
type: new GraphQLList(ImageType),
resolve(parentValue) {
return Post.findById(parentValue._id)
.populate('images') //incorrectly pointing towards the collection in db
.then(post => post.bodyImages)
}
},
})
})
module.exports = PostType;
It's supposed to point back in to the document/model itself:
const PostType = new GraphQLObjectType({
name: 'PostType',
fields: () => ({
_id: { type: GraphQLID },
mainImages: { //this is the 'path'
type: new GraphQLList(ImageType),
resolve(parentValue) {
return Post.findById(parentValue._id)
.populate('mainImages') //that's referenced here
.then(post => post.mainImages)
}
},
bodyImages: {
type: new GraphQLList(ImageType),
resolve(parentValue) {
return Post.findById(parentValue._id)
.populate('bodyImages') //and once again
.then(post => post.bodyImages)
}
}
})
})
export default PostType;
And that solved my problem, all of my GraphQL subqueries are working correctly now. Another day, another episode of I didn't read the docs correctly.

Related

insert to MongoDB array with axios, restAPI and nodeJS

I am trying to add an item to a MongoDB array with RESTAPI through Axios. I thought it would look similar to the push method but I have no idea how to do that.
my Model is of a person:
const Schema = mongoose.Schema;
const PersonSchema = new Schema({
name: String,
password: String,
friends: [],
missions: []
})
const personModel = mongoose.model('Person', PersonSchema);
I want to add a mission to the mission array of a person.
and for example, in order to add a new Person, I use NodeJS and API:
(api.js)
router.post('/api/people', (req, res) => {
const personToAdd = req.body;
const newPersonPost = new personModel(personToAdd);
newPersonPost.save((e) => {
if (e) {
console.log("error");
}
});
res.json({
msg: 'Received'
})
});
and in the client side I use Axios:
axios({
url: 'http://localhost:8080/api/people',
method: 'POST',
data: dataToUpdate
})
.then(() => {
console.log('axios sent info to server');
}).catch((e) => {
console.log('error' + e);
})
Thank you so much!
express
router.post('updating mission endpoint url', async (req, res) =>
try {
const query = { /* content */}; /* write a query to retrieve the concerned user by using a unique identifier */
let person = await personModel.findOne(query);
person.missions.push(req.body.mission);
personModel.save();
} catch (err) {
console.log(err);
}
});
client
In the client side you just have to put the mission you want to add in data like you did above with the right endpoint url and you should add a unique identifier for the user you want to add mission to.
[] will not assign array type to your variable.
Change your schema file with the following:
const Schema = mongoose.Schema;
const PersonSchema = new Schema({
name: { type: String },
password: { type: String },
friends: { type: Array },
missions: { type: Array }
})
Update the db model entity file with following
First method:
const Schema = mongoose.Schema;
const PersonSchema = new Schema({
name: String,
password: String,
friends: {type : Array},
missions: {type : Array}
})
const personModel = mongoose.model('Person', PersonSchema);
Second Method :
const Schema = mongoose.Schema;
const PersonSchema = new Schema({
name: String,
password: String,
friends: [{ type: String }],
missions: [{ type: String }]
})
const personModel = mongoose.model('Person', PersonSchema);
You can update the array object as per your requirements.
You just want to be using the $push update operator, very simple, like so:
db.collection.updateOne(
{
_id: user._id
},
{
"$push": {
"missions": {
mission: newMission
}
}
})
Mongo Playground

Cast to [ObjectId] failed for value

Please help me with this one. I have one post query and I want to save it into two tables seperately. But I encountered an error message from postman " Cast to [ObjectId] failed for value "
Please help me! Thank you!
Here is my code below:
FirstModel
const mongoose= require('mongoose');
const { Schema } = mongoose;
const class = require('./classModel');
const SubjectSchema = new mongoose.Schema({
name: {
type: String,
unique: true
},
address: {
type: String,
},
classes: [ { type: Schema.Types.ObjectId, ref: class.schema } ]
})
Second Model
const classSchema = new mongoose.Schema({
name: {
type: String,
unique: true
},
})
and here is my controller
exports.create = catchAsync (async (req, res, next) => {
const newSubject = await Subject.create(req.body)
let payload = {
body : {
...req.body.classes,
_id: new mongoose.Types.ObjectId(),
golf_course_id: newGolfCourse._id
}
}
const newClass = await Class.createClass(payload)
if(!newClass ) {
res.status(201).json({
status: 'success',
data : {
subject: newSubject
}
})
}
})
It's because you are trying to set your classes in your Subject before they have ObjectIds. In your Subject Schema you have the following line:
classes: [ { type: Schema.Types.ObjectId, ref: class.schema } ]
Which tells Mongoose that you are referencing another model with it's assigned ObjectId but in your case when you try to save it:
await Subject.create(req.body)
These classes don't have ID's yet, so you need to create them. I wouldn't manually set them, when mongoose sets them on it's own. So the following would be a suggestion.
try {
const classes = req.body.classes.map(async (className) => {
const newClass = new Class({ name: className });
newClass.golf_course_id = newGolfCourse._id;
await newClass.save();
return newClass;
});
const results = await Promise.all(classes);
const newSubject = await new Subject({
name: req.body.name,
address: req.body.address,
classes: results,
});
res.send(newSubject);
} catch (error) {
console.log(error);
}
This method creates the classes just like you had in your code but then it give the classes to the Subject after they have been created, that way the Subject can use them. Otherwise you will get an ObjectId error because you can't assign a field with a reference e.g. classes: [ { type: Schema.Types.ObjectId, ref: class.schema } ] without a reference to that item. By the way in your ref: class.schema just use ref: 'Class'

Product not updating - Mongoose MongoDB

I am unable to update and save a change in the database using mongoose. I am getting the same value for foundProduct twice when I console.log. What could be going wrong?
// Schema
const productSchema = new mongoose.Schema(
{
name: {
type: String,
required: true,
},
price: {
type: Number,
required: true,
},
onSale: {
type: Boolean,
default: false,
},
}
)
// model
const Product = mongoose.model('Product', productSchema)
const findProduct = async () => {
const foundProduct = await Product.findOne({ name: 'Mountain Bike' });
console.log(foundProduct)
foundProduct.OnSale = true;
await foundProduct.save().then((data) => console.log(data))
// console.log(foundProduct)
}
findProduct();
You have a typo. Change foundProduct.OnSale = true; with foundProduct.onSale = true;.
Since you are using the wrong case, Mongoose considers OnSale to be an extra field. And because it's not in your schema, it's ignored when saving to db.
You have a typo error in the foundProduct.OnSale instead of foundProduct.onSale

What should you check when graphQL's query return's empty data from mongodb?

I'm attempting to translate a Flask api to a graphql server. I can get example code to work with a test mongodb collection but I cannot get my adapted code to work on the real mongodb server. The query always returns an empty data response. What are the steps to debug this code?
const Express = require("express");
const { graphqlHTTP } = require('express-graphql');
const Mongoose = require("mongoose");
const {
GraphQLID,
GraphQLString,
GraphQLList,
GraphQLNonNull,
GraphQLObjectType,
GraphQLSchema
} = require("graphql");
var app = Express();
Mongoose.connect("mongodb://localhost/treasure-chess");
//"persons" is the collection
const GameModel = Mongoose.model("game", {
black: String,
white: String
});
const GameType = new GraphQLObjectType({
//the name field here doesn't matter I guess...
name: "Game",
fields: {
id: { type: GraphQLID },
black: { type: GraphQLString },
white: { type: GraphQLString }
}
});
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "Query",
fields: {
games: {
type: GraphQLList(GameType),
resolve: (root, args, context, info) => {
return GameModel.find().exec();
}
},
game: {
type: GameType,
args: {
id: { type: GraphQLNonNull(GraphQLID) }
},
resolve: (root, args, context, info) => {
return GameModel.findById(args.id).exec();
}
}
}
}),
mutation: new GraphQLObjectType({
name: "Mutation",
fields: {
game: {
type: GameType,
args: {
firstname: { type: GraphQLNonNull(GraphQLString) },
lastname: { type: GraphQLNonNull(GraphQLString) }
},
resolve: (root, args, context, info) => {
var game = new GameModel(args);
return game.save();
}
}
}
})
});
app.use("/graphql", graphqlHTTP({
schema: schema,
graphiql: true
}));
app.listen(3000, () => {
console.log("Listening at :3000...");
});
Proof that I'm connecting to the right mongodb document:
More proof:
I did attempt the comment suggestion only to find that the result was empty...:
var results = GameModel.find().exec()
results.then(game_info =>{
console.log(game_info)
})
I found the answer after trying a bunch of different searches on stackoverflow. It turns out that mongoose.model assumes that whatever parameter you pass is singular and pluralizes it by default. So I actually had to pass in:
const GameModel = Mongoose.model("game", gameSchema, "game");
Granted most people probably won't experience this error as they probably named their collections with this in mind, there might be the one odd person who wanted to utilize a different name for their collection OR the plural of the collection is the same singular. I'll leave this up for others, happy coding!

How to get the Populated values of a model in GraphqQL list without _id reference?

I have a Users model with some fields and reference to "Skills" model.
The models are look like this below
1.Users.js (users model)
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const UsersSchema = new Scheam({
name: { type : String },
email: { type : String },
address: { type : String },
Skills: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Skills' }],
})
module.exports = mongoose.model('Users', UsersSchema);
2.Skills Model
const mongoose = require('mongoose')
const Schema = mongoose.Schema;
const SkillsSchema = new Schema({
name : { type: String },
});
module.exports = mongoose.model('Skills', SkillsSchema);
I am creating the new user with dropdown menu of skills selection storing the id's of the skills in users model.
And getting the skills by populating the Users model like this in node js app.
export function getUser(req, res) {
console.log('getUser');
console.log('req.body',req.body);
Users.findOne({_id: req.body.user_id}).populate('skills').exec(function(err, usr_doc){
if(err) {
res.json({
status: 401,
message: 'something went wrong!',
err: err,
});
} else if (!err && usr_doc != null) {
res.json({
status: 200,
message: 'User details found!',
data: {_id: usr_doc._id, skills: usr_doc.skills,name: usr_doc.name,token: usr_doc.token},
})
}
})//Users.findOne end
}
The above code is working fine for normal rest api.
Here I try to create the Same api using GraphQL server of express-graphql in Nodejs.
The code look like this.
3.Schema.js
const graphql = require('graphql');
const Users = require('../models/Users');
const Skills = require('../models/Skills');
const {
GraphQLObjectType,
GraphQLInt,
GraphQLFloat,
GraphQLString,
GraphQLSchema,
GraphQLID,
GraphQLList,
GraphQLNonNull,
GraphQLBoolean,
GraphQLObject
} = graphql;
const UsersType = new GraphQLObjectType ({
name: 'Users',
fields: () => ({
id: {type: GraphQLID},
name: { type : GraphQLString },
email: { type : GraphQLString },
**skills: {
type: new GraphQLList(SkillsType),
resolve(parent, args){
//Here I need to know how to get the skills name of the users
}
}**
})
const SkillsType = new GraphQLObjectType({
name: 'Skills',
fields: () => ({
name: { type : GraphQLString },
})
})
At present I didn't use any reference id in skills model, only name. I want to get the skill names of the Users.
If i use the below, I am getting all of the skill names instead of user particular or populated skill names.
skills : {
type: new GraphQLList(SkillsType),
resolve(parent, args){
return Skills.find({})
}
Please provide any suggestions regarding this.
I can give more information if you need.
To get the skills of a user, instead of Skills.find({}) which returns all the documents present in the skills collection, add a condition there with the parent object.
The parent object, which contains result from the resolver on the parent field here. So skills, a child resolver, can be written as:
skills: {
type: new GraphQLList(SkillsType),
resolve(parent, args) {
return await Skills.find({"_id": {$in: parent.skills} }) }
}

Resources