Get populated data from Mongoose to the client - javascript

On the server, I am populating user-data and when I am printing it to the console everything is working fine but I am not able to access the data on the client or even on Playground of GraphQL.
This is my Schema
const { model, Schema } = require("mongoose");
const postSchema = new Schema({
body: String,
user: {
type: Schema.Types.ObjectId,
ref: "User",
},
});
module.exports = model("Post", postSchema);
const userSchema = new Schema({
username: String,
});
module.exports = model("User", userSchema);
const { gql } = require("apollo-server");
module.exports = gql`
type Post {
id: ID!
body: String!
user: [User]!
}
type User {
id: ID!
username: String!
}
type Query {
getPosts: [Post]!
getPost(postId: ID!): Post!
}
`;
Query: {
async getPosts() {
try {
const posts = await Post.find()
.populate("user");
console.log("posts: ", posts[0]);
// This works and returns the populated user with the username
return posts;
} catch (err) {
throw new Error(err);
}
},
}
But on the client or even in Playground, I can't access the populated data.
query getPosts {
getPosts{
body
user {
username
}
}
}
My question is how to access the data from the client.
Thanks for your help.

you are using this feature in the wrong way you should defined a Object in your resolvers with your model name and that object should contain a method that send the realated user by the parant value.
here is a full document from apollo server docs for how to use this feature

use lean() like this :
const posts = await Post.find().populate("user").lean();

Related

mongoose: save is not a function

Given a user model:
import { model, Schema } from 'mongoose'
export interface User {
email: string
}
const userSchema = new Schema<User>(
{
email: {
type: String,
required: true,
},
},
)
export const UserModel = model<User>('User', userSchema)
I'm trying to save it as so:
// inside an async function
const newUser: HydratedDocument<User> = new UserModel({
email: 'aaa#aaa.com',
})
console.log(newUser)
await newUser.save()
Which results in newUser.save is not a function. What am I missing? Also, here is the output of the `console.log(newUser)
My stupid mistake: I was calling everything on the fronted. Solution is as easy as moving relevant db calls to the endpoint.
You will need to also define a model, change your schema definition to this:
first create model from Schema :
var UserModel = mongoose.model('User', User);
then create object out of User model
var user = new UserModel(req.body)
then call
user.save(function(){});
check documentation http://mongoosejs.com/docs/api.html#model_Model-save

How to get the id of a document before even saving it in mongoose?

I have a simple controller that creates a post for a user. Another schema is linked to it. When I try to create a new post, I need to get the id of the post so that I can link other schema to it.
Here is the schema:
const mongoose = require("mongoose");
const User = require("./User");
const View = require("./View");
const ArticleSchema = new mongoose.Schema({
title: {
type: String,
required: true,
trim: true,
},
body: {
type: String,
required: true,
},
status: {
type: String,
default: "public",
enum: ["public", "private"],
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
views: {
type: mongoose.Schema.Types.ObjectId,
ref: "View",
},
createdAt: {
type: Date,
default: Date.now,
},
});
module.exports = mongoose.model("Article", ArticleSchema);
It is fine when I want to link the user field because I have that stored in memory.
But the view field requires postId of that particular document. And I can't get it without first creating the document.
My create post controller:
module.exports.createArticleController = async function (req, res) {
try {
req.body.user = req.User._id;
const article = await Article.create(req.body).exec()
res.redirect(`/${article.title}/${article._id}`);
} catch (e) {
console.error(e);
}
};
So my question is,
How can i get the id in the process of executing the model.create() so that i can link the view to that id. Maybe something using the this operator
I don't want to use update after create.
You can generate your own id and save it
ObjectId id = new ObjectId()
You can get object Id's right after creating an instance of Model or create your own object id's and save them.
Here's how i achieved it:
module.exports.createArticleController = async function (req, res) {
try {
const instance = new Article();
instance.title = req.body.title;
instance.body = req.body.body;
instance.status = req.body.status;
instance.user = req.User._id;
instance.views = instance._id;
const article = await instance.save();
if (article) {
res.redirect(`/${article.title}/${article._id}`);
}
} catch (e) {
console.error(e);
}
};
Or you can create them and save it to the db.
var mongoose = require('mongoose');
var myId = mongoose.Types.ObjectId();
const instance = new YourModel({_id: myId})
//use it
Continue reading
How do I get the object Id in mongoose after saving it.
Object Id's format and usage
You can just simply create a schema ovject like this:
const task: TaskDocument = new this.taskSchema({ ...createTaskDto })
This is from one of my projects, since the ObjectId from MongoDB is based on the operating machine and the time it is created, it doesn't need the database to generate the id.
You can now access task._id to get your id without saving it.

Mongoose populate returns empty array or list of ObjectIds

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.

Retrieving user data from mongoDB

I'm building an app where a user logs in and can create a grocery list on their account (there are more things they can do like create recipes, but this is the example I want to use). Right now I have it so everybody who logs in sees the same list. But I want each user to be able to log in and view their own grocery list that they made. I'm assuming the logic is literally like logging into a social media site and viewing YOUR profile, not somebody else's.
I'm using mongoDB/mongoose and I just read about the populate method as well as referencing other schemas in your current schema. Here is my schema for the list:
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
// Create item schema
const GroceryListItemSchema = new Schema({
item: {
type: String,
required: [true, 'Item field is required']
},
userId: {
type: mongoose.Schema.Types.ObjectId, ref: "user",
}
});
// Create an Item model
const GroceryListItem = mongoose.model('groceryListItem', GroceryListItemSchema);
module.exports = GroceryListItem;
And here is the post request to add a list item:
//POST request for shopping list
router.post("/list", checkToken, (req, res, next) => {
// Add an item to the database
const groceryListItem = new GroceryListItem({
item: req.body.item,
userId: ???
})
groceryListItem.save()
.then((groceryListItem) => {
res.send(groceryListItem);
})
.catch(next);
});
Here is my userModel - not sure if this is necessary to show:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const UserSchema = new Schema({
username: {
type: String,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
password2: {
type: String,
required: true,
},
});
const User = mongoose.model("users", UserSchema);
module.exports = User;
(in case anyone is wondering why the model is called "users"-- that's what I initially called it on accident and when I changed the name to "user" it errored out...so I changed it back.)
I am not sure how to add the userId when making an instance of the groceryListItem. In the mongoose docs (https://mongoosejs.com/docs/populate.html#saving-refs), they use the example of a Story and Person Schema. They reference each other, and then they create an instance of Person, calling it author. Then they grab the _id from author and reference it in their instance of Story, called story1. So that makes sense to me. But the only way they can do that is because author and story1 are located in the same file.
So it seems like what I should do is grab the user _id by saying userId: users._id. But my new User instance is in my user routes. And I'd rather not combine the two. Because then I'd have another list to combine as well so that would be my user routes, recipe routes, and shopping list routes all in one file and that would be extremely messy.
Anyone have any idea how I can make this work? It seems so simple but for some reason I cannot figure this out.
Thank you!!
EDIT - frontend API call:
handleSubmitItem = (e) => {
e.preventDefault();
const newItem = {
item: this.state.userInput,
};
authAxios
.post(`http://localhost:4000/list/${userId}`, newItem)
.then((res) => {
this.setState({ items: [...this.state.items, newItem] });
newItem._id = res.data._id;
})
.catch((err) => console.log(err));
this.setState({ userInput: "" });
};
Here you can simply pass in the user ID in the POST request params. The POST URL in the frontend should look like this; {localhost:9000/like/${userID}}
You can get the user ID at the express backend like this;
router.post("/list/:id", checkToken, (req, res, next) => {
// Add an item to the database
const groceryListItem = new GroceryListItem({
item: req.body.item,
userId: req.params.id
})
groceryListItem.save()
.then((groceryListItem) => {
res.send(groceryListItem);
}).catch(next);
});

Apollo makeExecutableSchema throws error "Cannot read property 'kind' of undefined"

I am trying to do schema stitching with apollo but I am getting an error every time I use makeExecutableSchema. The error is the following:
../node_modules/graphql-tools/dist/generate/concatenateTypeDefs.js:9:
-> if (typeDef.kind !== undefined)
TypeError: Cannot read property 'kind' of undefined
I have reproduced the problem even when just copying the basic example on Apollo's website
const { ApolloServer, gql, makeExecutableSchema } = require("apollo-server");
const { addMockFunctionsToSchema, mergeSchemas } = require("graphql-tools");
const chirpSchema = makeExecutableSchema({
typeDefs: `
type Chirp {
id: ID!
text: String
authorId: ID!
}
type Query {
chirpById(id: ID!): Chirp
chirpsByAuthorId(authorId: ID!): [Chirp]
}
`
});
addMockFunctionsToSchema({ schema: chirpSchema });
const authorSchema = makeExecutableSchema({
typeDefs: `
type User {
id: ID!
email: String
}
type Query {
userById(id: ID!): User
}
`
});
addMockFunctionsToSchema({ schema: authorSchema });
const schema = mergeSchemas({
schemas: [chirpSchema, authorSchema]
});
const server = new ApolloServer(schema);
server.listen().then(({ url }) => {
console.log(`🚀 Server ready at ${url}`);
});
What am I doing wrong? However I try I always get the same error when using makeExecutableSchema.
From what i see you are using apollo-server#2.0.0-rc
in this version
ApolloServer constructor receives an options parameter
constructor(options)
you should pass new ApolloServer({ schema: schema }) instead of new ApolloServer(schema)
i tried that with the example you gave and it worked :)

Categories

Resources