GraphQL relationship in Mongoose - javascript

I have the following GraphQL Schema
type User {
id: String!
name: String
username: String!
}
type Conversation {
id: String!
participants: [User]
}
type Query {
user(_id: String!): User
conversation(_id: String!): Conversation
}
My resolver for the conversation is as follows:
conversation: async (parent, args) => {
let conversation = await Conversation.findById(args._id);
conversation.id = conversation._id.toString();
return conversation;
}
The participants field will hold array of users ObjectId. What do I need to do in my resolver so I can fetch users data within the conversation call.
For example a call like this
query test($id:String!){
conversation(_id:$id){
id,
participants {
id,
username
}
}
}

You probably used Reference in your object model, therefore in order to fetch participants data, you should use mongoose populate!
This would work for you:
conversation: async (parent, args) => {
let conversation = await Conversation.findById(args._id).populate('participants');
conversation.id = conversation._id.toString();
return conversation;
}

Related

Fetch nested items using GraphQL query in JavaScript Amplify

Definition:
A Team can have many Users and 1 user as manager:
type Team {
id: ID!
title: String
manager: User #connection
users: [User] #connection(name: "TeamUsers")
}
The User type is defined as:
type User {
id: ID!
username: String
team: [Team] #connection(name: "TeamUsers")
}
Using JS, I can access the Team manager's username:
{teams.map((team) => {
return(
<li>{team.manager.username}</li>
)
}
Problem: I can't get the usernames of users in the team using either:
{teams.map((team) => {
return(
<li>{team.users.username}</li>
// or
<li>{team.users.map((user) => {user.username})}</li>
)
}
PS) I am using AWS Amplify and I'm fetching teams using the following code:
const fetchTeams = async () => {
try {
const teamsData = await API.graphql(graphqlOperation(listTeams));
const teamsList = teamsData.data.listTeams.items;
setClients(teamsList);
} catch (error) {
console.error(`fetchTeams failed.`, error);
}
};
As #Bergi said in the comment.
I think
<li>{team.users.map((user) => {user.username})}</li>
=>
<li>{team.users.map(user => user.username)}</li>
will fix your issues

Get populated data from Mongoose to the client

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();

Firestore Nested Documents with random ID inside a collection document

I am creating an application using Angular and firebase. Where I want to create a collection called PETS inside that collection the document ID will be currently logged in users UID. And inside that Document I want to create documents with random IDs based on my Form values.
-|pets
-|userId
-|RandomId which contains all the form values
-|RandomId which contains all the form values
Right Now I am using this method. Please tell me what are the changes that I need to make here. This method creating a collection with same ID as current users. But it's not creating separate documents inside the parent Document with users ID. Also I want to loop over those documents with random Ids and will show a list of PETS in fronted.
addPet(ownerName, PetName, type, breed, size){
let user = this.afAuth.auth.currentUser;
if (user) {
return new Promise((resolve, reject) => {
this.afs.collection("pets").doc(user.uid).set({
OwnerName: ownerName,
Petname: PetName,
PetType: type,
PetBreed: breed,
PetSize: size
})
.then((response) => {
resolve(response)
})
.catch((error) => {
reject(error)
});
})
} else {
alert('user not logged in')
}
}
Currently you wish to store a user's pets at /pets/userId. To add each pet at this location, you would need to use a subcollection as documents can't hold other documents. The following code adds a pet that will be stored at /pets/userId/pets/somePetId.
addPet(ownerName, petName, type, breed, size){
let user = this.afAuth.auth.currentUser;
if (!user) {
return Promise.reject({code: 'unauthenticated', message: 'user not logged in'})
}
return this.afs.collection("pets").doc(user.uid).collection("pets").add({
OwnerName: ownerName,
OwnerID: user.uid,
PetName: petName,
PetType: type,
PetBreed: breed,
PetSize: size
});
}
However you have two other ways you could model this data.
Global Pets Collection
Instead of saving pets under a userId, instead each pet has their own ID and you link that pet to their owner's ID.
addPet(ownerName, petName, type, breed, size){
let user = this.afAuth.auth.currentUser;
if (!user) {
return Promise.reject({code: 'unauthenticated', message: 'user not logged in'})
}
return this.afs.collection("pets").add({
OwnerName: ownerName,
OwnerID: user.uid,
PetName: petName,
PetType: type,
PetBreed: breed,
PetSize: size
});
}
To get the an array of pets for a given user, you would use the following:
function getPetsForCurrentUser() {
let user = this.afAuth.auth.currentUser;
if (!user) {
return Promise.reject({code: 'unauthenticated', message: 'user not logged in'})
}
return this.afs.collection("pets").where("OwnerID", '==', user.uid).get()
.then(childrenAsArrayOfObjects)
}
Pets Subcollection of User
Because you are using Cloud Firestore and not the Realtime Database, you can save each pet as a subcollection of the owner's user data. Unlike the Realtime Database, when you fetch the data of /users/userId, you only get the data of that specific document and not all the data nested under that path. The code below assigns each pet their own ID and saves it to a subcollection of it's owner:
addPet(ownerName, petName, type, breed, size){
let user = this.afAuth.auth.currentUser;
if (!user) {
return Promise.reject({code: 'unauthenticated', message: 'user not logged in'})
}
return this.afs.collection("users").doc(user.uid).collection("pets").add({
OwnerName: ownerName,
OwnerID: user.uid,
PetName: petName,
PetType: type,
PetBreed: breed,
PetSize: size
});
}
To get the an array of pets for a given user, you would use the following:
function getPetsForCurrentUser() {
let user = this.afAuth.auth.currentUser;
if (!user) {
return Promise.reject({code: 'unauthenticated', message: 'user not logged in'})
}
return this.afs.collection("users").doc(user.uid).collection("pets").get()
.then(childrenAsArrayOfObjects)
}
With this data structure, if you wanted to query all pets, regardless of their owner, you would make use of a collection group query.
let ragdollCatsQuery = db.collectionGroup('pets').where('PetType', '==', 'Cat').where('PetBreed', '==', 'Ragdoll');
ragdollCatsQuery.get()
.then(querySnapshot => {
console.log("Found " + querySnapshot.size + " cats with the breed: Ragdoll");
})
Other Notes
Please use a consistent casing style to prevent errors and typos. e.g. Change Petname to PetName to match the database key OwnerName's TitleCase. Similarly, the convention is to use camelCase for variable names, so the variable PetName should be petName.
The above functions both use the following transformation function:
function childrenAsArrayOfObjects(querySnapshot, idFieldName) {
let idFieldName = (idFieldName && idFieldName + "") || "id";
let children = [];
querySnapshot.forEach(childDoc => {
children.push({...childDoc.data(), [idFieldName]: childDoc.id})
}
return children;
}
If you wanted to use a different field name for the document ID when using this transformation, you would use:
.then(querySnapshot => childrenAsArrayOfObjects(querySnapshot, "PetID"))
You can find other transforms here.

Apollo GraphQL Nested Mutation

need some help on nested mutations.
Abstracted scenario is this:
I want to combine 2 mutation calls on apollo-server to first create a a Customer then create an Address for that customer. The Address mutation needs a customerID to be able to do this but also has information from the original overall mutation that it needs access to.
Here's the generic code:
makeExecutableSchema({
typeDefs: gql`
type Mutation {
createCustomerWithAddress(customer: CustomerRequest!, address: AddressRequest!): Response
}
input CustomerRequest {
name: String!
}
input AddressRequest {
address: String!
city: String!
state: String!
country: String!
}
type Response {
customerID: Int!
addressID: Int!
}
`,
resolvers: {
Mutation: {
createCustomerWithAddress: async (_, {customer}, context, info) => {
return await api.someAsyncCall(customer);
}
},
Response: {
addressID: async(customerID) => {
// how do we get AddressRequest here?
return await api.someAsyncCall(customerID, address);
}
}
}
})
There's a lot of complexity I'm not showing from the original code, but what I wanted to get at is just at the root of how to access request params via sub mutations, if even possible. I don't really want to pass down address from the top mutation to the sub mutation.
You don't need a Response field in resolvers. createCustomerWithAddress should return an object shaped like Response.
resolvers: {
Mutation: {
createCustomerWithAddress: async (_, {customer, address}, context, info) => {
// create customer
const customerId = await api.CreateCustomer(customer);
// create address and assign customerId
const addressId = await api.CreateAddress({ ...address, customerId });
// return response
return { customerId, addressId };
}
},
}

TypeORM Apollo nested query resolver

I have a schema (with the appropriate database tables and entity classes defined) like
type User {
id: Int!
phoneNumber: String!
}
type Event {
id: Int!
host: User
}
and I'm trying to use Apollo to write a query like
Query{
event(id:1){
host{
firstName
}
}
}
But I can't figure out how to get the Apollo library to resolve the User type in the host field to the hostId that is stored on the event object.
I modified the event to return the hostId field, and it works perfectly fine, but Graphql won't resolve the id to the appropriate user type. What am I missing?
edit: missing resolver code
event: async (parent: any, args: { id: number }) => {
const eventRepository = getConnection().getRepository(Event);
const event = await eventRepository.findOne(args.id);
return event;
},
I managed to get a working version by using findOne(args.id, { relations: ['host']}), but I don't like that because it seems like something that would be appropriate to delegate to graphql to handle.
Your resolver should be like that
const resolver = {
Query: {
event: async (_: any, args: any) => {
return await event.findOne(args.id);
}
},
event: {
host: async (parent: any, args: any, context: any) => {
return await user.find({ id: parent.id });
}
}
};

Categories

Resources