GraphQL and Mongoose: When querying, populate references in subdocuments - node.js

This is my schema, and in MongoDB I have separate collections for users, events and movies due to the large number of events and movies:
type Event {
id: ID!
title: String!
description: String!
creator: User!
}
type Movie {
id: ID!
title: String!
releaseDate: String
}
type MovieStatus {
movie: Movie
status: StatusEnum // WATCHED, PLANNING_TO_WATCH
}
type User {
id: ID!
firstName: String!
createdEvents: [Event!]
movies: [MovieStatus!]
}
And I want to create the following query:
query {
user(id: "12345") {
id
firstName
createdEvents{
id
title
description
}
movies{
movie {
id
title
releaseDate
}
status
}
}
}
I have no problem getting the events, by including this in my resolvers:
User: {
createdEvents: ({ createdEvents }) =>
Event.find({ _id: { $in: createdEvents } }),
},
But I can't figure out how to access the movie id, title and release dates.

movies: ({ movies }) => {
return Promise.all(
movies.map(async (m) => {
const movie = m
movie.movie = await Movie.findById(movie.movie)
return movie
})
)
}

Related

GraphQL/ Apollo Server: Can't Access nested object

Problem: Trying to query for products in user's basket breaks with the following error:
"ID cannot represent value: <Buffer 60 41 24 0c ae a8 b6 35 ac 33 5a cd>"
my query:
query getBasket{
getBasket {
id
quantity
createdAt
product{
id <--- produces error
title <--- removing ID, product.title becomes null
stock <--- becomes null if ID is removed from query
}
}
}
If I omit the product field, everything works as expected but I need to be able to display the product information for the user's basket. When I try retrieve the product information I get the error.
My GraphQL definitions are the following (I am new to this), there should be a one to many relationship between User and Item (which represents the items in their basket):
module.exports = gql(`
enum Role {
ADMIN
USER
}
type Item {
id: ID!
product: Product!
quantity: Int!
createdAt: String!
}
type User {
id: ID!
email: String!
token: String!
roles: [Role]!
basket: [Item]!
createdAt: String!
}
type Product {
id: ID!
title: String!
description: String!
stock: Boolean
price: String
}
input RegisterInput {
email: String!
password: String!
confirmPassword: String!
}
type Query {
getProducts: [Product]
getProduct(productId: ID!): Product
getBasket: [Item]
getUsers: [User]
}
type Mutation {
register(registerInput: RegisterInput): User!
login(email: String!, password: String!): User!
createProduct(title: String!, description: String, stock: Boolean, price: String!): Product!
deleteProduct(productId: ID!): String!
addToBasket(productId: String!, quantity: String!): User!
deleteFromBasket(productId: String!): User!
}
`);
I am able to add products into the basket, it's just when I try to retrieve the user's basket I get the error.
Click this to see how my data looks like on MongoDB atlas: https://i.stack.imgur.com/RKcnP.png
There's only a couple of posts about the error I tried converting the string ID to object ID. It could be a problem with GraphQL? I'm unsure, perhaps I need to redo my GraphQL Definitions.
User Schema:
const userSchema = new Schema({
email: String,
password: String,
roles: [String],
basket: [
{
product: {
type: Schema.Types.ObjectId,
ref: 'products'
},
quantity: String,
createdAt: String
}
],
createdAt: String
});
AddToBasket Mutation:
Mutation: {
addToBasket: async (_, {productId, quantity}, context) => {
// TODO: Validate input fields
console.log("adding to basket")
const userContext = checkAuth(context, USER);
const user = await User.findById(userContext.id);
const product = await Product.findById(productId);
if (product) {
user.basket.unshift({ //this adds the following into the basket as an object
product: product.id,
quantity,
createdAt: new Date().toISOString()
});
await user.save();
return user;
} else throw new UserInputError('Product not found');
}
}
Thank you for helping!
Edit 11:51 06/03/2021: (removed)
Edit 2 12:25 06/03/2021:
GraphQLError: ID cannot represent value: <Buffer...>"
My problem is the exact same problem as this persons but I think their code is different to mine? How I return the user's basket is by the following:
Query: {
getBasket: async (_, {}, context) => {
console.log("RUNNING");
const userContext = checkAuth(context, USER);
const user = await User.findById(userContext.id);
return user.basket;
}
},
Edit 3 12:52 06/03/2021:
Here is my user Schema:
const { model, Schema } = require('mongoose');
const userSchema = new Schema({
email: String,
password: String,
roles: [String],
basket: [
{
product: {
type: Schema.Types.ObjectId,
ref: 'products'
},
quantity: String,
createdAt: String
}
],
createdAt: String
});
module.exports = model('User', userSchema);
my schema references were wrong, I figured that out after reading one of the answers for this post: MissingSchemaError: Schema hasn't been registered for model "User"
basket: [
{
product: {
type: Schema.Types.ObjectId,
ref: 'products' <--- changed to 'Product' (the name of my 'Product'Schema)
},
quantity: String,
createdAt: String
}
],
I figured out that my nested objects weren't populated, so I read this for better understanding: https://dev.to/paras594/how-to-use-populate-in-mongoose-node-js-mo0 . I figured this out by console.logging the user.basket value (and other fields) before returning user.basket, I found that the user.basket was undefined.
I redid my getBasket Query to the following, here I'm ensuring I'm populating the basket field and the product field inside the basket field.
Without .populate, the error "ID cannot represent value: <Buffer..." would prop up again.
Query: {
getBasket: async (_, {}, context) => {
console.log("RUNNING");
const userContext = checkAuth(context, USER);
const user = await User.findById(userContext.id, {basket:1})
.populate({
path: "basket", // populate basket
populate: {
path: "product", // in basket, populate products
select: {title:1, stock:1, price:1}
}
})
.exec();
return user.basket;
}
},
If your query result is null, then read this answer for better understanding: Why does a GraphQL query return null?.
ANSWER = POPULATE THE NESTED FIELD
Feel free to post any improvements or changes that could be made.
Thank you Ezra for helping me!

My graphql server query is returning null value

I wrote a graphql API that connects to a Mongo database allocated in my machine using the MongoDB driver, and have a Query resolver
getServiceByType: async (_, args) => {
const serviceData = await db
.collection("service")
.find({ type: args.type })
.toArray();
let services = [];
return await serviceData.map(async (value) => {
const dbUser = await db
.collection("user")
.findOne(ObjectID(value.user));
const userFiltered = pick(dbUser, ["username", "email"]);
const { user, ...withoutUser } = value;
const filledValue = { ...withoutUser, user: userFiltered };
services.push(filledValue);
console.log(services);
return services;
});
},
That should return the value for the Service schema and the User related with it.
The console.log(services) shows the result that i expect
[
{
_id: 5f1ca8e2d866d3536c102e07,
name: 'Second Service',
description: 'Creating second service for test',
type: 'Food',
user: { username: 'TestUser', email: 'Test#test.com' }
}
]
But in the Graphql Playground throws the following error message:
"message": "Cannot return null for non-nullable field Service._id."
I don't know what's wrong with the Query resolver.
edit
Result type def looks like this:
type User {
_id: ID!
name: String!
username: String!
password: String!
email: String!
type: String!
country: String!
transactions: Int
}
type Service {
_id: ID!
name: String!
description: String!
user: User!
type: String!
}

i am getting "cannot read property _doc of null" i am trying to fetch the id of a user currently logged in and pass to the creator

below is my schema:
module.exports = buildSchema(`
type Booking {
_id: ID!
event: Event!
user: User!
createdAt: String!
updatedAt: String!
}
type Event {
_id: ID!
title: String!
description: String!
price: Float!
date: String!
creator: User!
}
type User {
_id: ID!
email: String!
password: String!
createdEvents: [Event!]
}
type authData {
userId:ID!
token:String!
tokenExpiriation: Int!
}
input EventInput {
title: String,
description: String!,
date: String,
price:Float!
}
input UserInput {
email: String!
password: String!
}
type RootQuery {
events: [Event!]!
bookings: [Booking!]!
login(email:String!, password:String!): authData!
}
type RootMutation {
createEvent(eventInput:EventInput): Event
createUser(userInput:UserInput): User
bookEvent(eventId: ID!): Booking!
cancelBooking(bookingId: ID!): Event!
}
schema {
query: RootQuery
mutation: RootMutation
}
`)
Events.js
module.exports = {
events: async () => {
try {
const events = await Event.find();
return events.map((event) => {
return eventsTemplate(event);
});
} catch (err) {
throw err;
}
},
createEvent: async (args) => {
const event = new Event({
title: args.eventInput.title,
description: args.eventInput.description,
price: +args.eventInput.price,
date: new Date(args.eventInput.date),
creator:req.userId,
});
let createdEvent;
try {
const result = await event.save();
createdEvent = eventsTemplate(result);
const creator = await User.findById(req.userId);
if (!creator) {
throw new Error("User not found.");
}
creator.createdEvents.push(event);
await creator.save();
return createdEvent;
} catch (err) {
console.log(err);
throw err;
}
},
};
my eventsTemplate is below
const eventsTemplate = (event) => {
return {
...event._doc,
_id: event.id,
date: dateToString(event._doc.date),
creator: user.bind(this, event.creator),
};
};

TypeError: String cannot represent value: graphql Query not working

I am trying to run a graphql Query but it keeps giving me the "TypeError: String cannot represent value:" error.
The schema for my query:
type User {
active: Boolean!
email: String!
fullname: String!
description: String!
tags: [String!]!
}
type Query {
getAllUsers: [User]!
}
My resolver:
Query: {
getAllUsers: (_, __, { dataSources }) => {
return dataSources.userAPI.getAllUsers();
}
}
userAPI:
getAllUsers() {
const params = {
TableName: 'Users',
Select: 'ALL_ATTRIBUTES'
};
return new Promise((resolve, reject) => {
dynamodb.scan(params, function(err, data) {
if (err) {
console.log('Error: ', err);
reject(err);
} else {
console.log('Success');
resolve(data.Items);
}
});
});
}
The query:
query getAllUsers{
getAllUsers{
email
}
}
Since my email is a string, the error I'm getting is "String cannot represent value".
What's returned inside your resolver should match the shape specified by your schema. If your User schema is
type User {
active: Boolean!
email: String!
fullname: String!
description: String!
tags: [String!]!
}
then the array of Users you return should look like this:
[{
active: true,
email: 'kaisinnn#li.com',
fullname: 'Kaisin Li',
description: 'Test',
tags: ['SOME_TAG']
}]
The data you're actually returning is shaped much differently:
[{
active: {
BOOL: true
},
description: {
S: 'Test'
},
fullname: {
S: 'Kaisin Li'
},
email: {
S: 'kaisinnn#li.com'
},
}]
You need to either map over the array you're getting from the scan operation and transform the result into the correct shape, or write a resolver for each individual field. For example:
const resolvers = {
User: {
active: (user) => user.active.BOOL,
description: (user) => user.description.S,
// and so on
}
}

How do I query nested documents related by ObjectID with GraphQL

USER has a field 'groups' which is an array of objectID's corresponding to GROUP
GROUP has a field 'boards' which is an array of objectID's corresponding to BOARD
BOARD has a field 'posts' which is an array of objectID's corresponding to POST
POST has a field 'comment' which is an array of objectID's corresponding to COMMENT
My goal here is to be able to query GrapQL like so:
Query{
user('_id'){
username
groups{
name
boards{
name
posts{
comment{
text
}
}
}
}
}
}
So by only supplying the query with the user's ID, I can get all the groups, boards, posts, and comments relative to the user.
My Type Defs are as follows:
type Query {
user(_id: String): User
groups(_id: String): [Group]
boards(_id: String): [Board]
posts(_id: String): [Post]
comments(_id: String): [Comment]
}
type User {
_id: String,
username: String,
email: String,
Groups: [Group]
}
type Group {
_id: String,
name: String,
bio: String,
owner: User
members: [User]
boards: [Board]
}
type Board {
_id: String,
name: String,
bio: String,
group: Group
posts: [Post]
}
type Post {
_id: String,
name: String,
board: Board,
comments: [Comment]
}
type Comment {
_id: String
text: String
}
schema {
query: Query
}
My resolvers (where I'm sure the problem lies):
const resolvers = {
Query: {
user: async (root, { _id }) => {
return prepare(await Users.findOne(ObjectId(_id)))
},
groups: async (root, { _id }) => {
return prepare(await Groups.findOne(ObjectId(_id)))
},
boards: async (root, { _id }) => {
return prepare(await Boards.findOne(ObjectId(_id)))
},
posts: async (root, { _id }) => {
return prepare(await Posts.findOne(ObjectId(_id)))
},
comments: async (root, { _id }) => {
return prepare(await Coments.findOne(ObjectId(_id)))
}
},
User: {
groups: async ({_id}) => {
return (await Groups.find({_id: _id}).toArray()).map(prepare)
}
}
}
Basically I'm trying to makle a big, beefy, nested query with a single call. All the models are related by objectID's. Does anyone have any input on how to proceed here or perhaps some links to related documentation? I've searched all over, but can't seem to find docs related to this specifically. Querying related documents by objectID.

Resources