0

I'm new to Amplify, React, and GraphQL. I'm looking to figure out the right way to make a mutation that has an @hasMany relationship between tables (for context, I am using a multi-table solution since that seems to work best with Amplify).

I'm trying to build an app that has a user, a couple of children for each user, and stories for each child... and I'm completely stumped on how to make this @hasMany connection between multiple tables for both create and update.

Here's my schema.graphql:

type User @model @auth(rules: [{allow: owner}]) {
  id: ID!
  email: String! @index(name: "byEmail", queryField: "userByEmail", sortKeyFields: ["id"])
  children: [Child] @hasMany
  settings: String
  lastInteraction: String
}

type Child @model @auth(rules: [{allow: owner}]) {
  id: ID!
  name: String!
  age: String!
  commonNoun: String!
  user: User @belongsTo
  stories: [Story] @hasMany
}

type Story @model @auth(rules: [{allow: owner}]) {
  id: ID!
  child: Child @belongsTo
  status: String
  storyType: String
  storyData: String!
}
 

I've been able to successfully create a user and a child, but now I want to link the two. Here's my code so far for creating a User:

export async function createNewUserinDB(userEmail) {
      
  const data = {
    email: userEmail
  };

  const userCreationResponse = await API.graphql({
    query: createUser,
    variables: { input: data },
    authMode: "AMAZON_COGNITO_USER_POOLS"
  }).then(response => {
    console.log(response);
    return response;
  }).catch(e => {
    console.log("catch");
      console.log(e);
      return null;
  });

  return(userCreationResponse);

}

And for creating a Child:

async function createChildinDB(childAge, childName, childCommonNoun, userID) {
      
    const data = {
      age: childAge,
      name: childName,
      commonNoun: childCommonNoun,
    };

    const childCreationResponse = await API.graphql({
      query: createChild,
      variables: { input: data },
      authMode: "AMAZON_COGNITO_USER_POOLS"
    }).then(response => {
      console.log(response);
      return response;
    }).catch(e => {
        console.log(e);
        return null;
    });

    return(childCreationResponse);

  }

I've tried creating a separate function to pass the already created Child into a UserUpdate, and I can't seem to get it right. I get this error when I execute the following code: "The variables input contains a field name 'children' that is not defined for input object type 'UpdateUserInput' "

export async function addChildtoUserInDB(userID, userEmail, childData) {
      
  const data = {
    id: userID,
    email: "Sparky", // this was for testing; works fine if this line exists but no line below
    children: [childData]
  };

  console.log(childData);

  const updateUserResponse = await API.graphql({
    query: updateUser,
    variables: { input: data },
    authMode: "AMAZON_COGNITO_USER_POOLS"
  }).then(response => {
    console.log(response);
    return response;
  }).catch(e => {
    console.log("catch");
      console.log(e);
      return null;
  });

  return(updateUserResponse);

}

The code works just fine if I comment out the line about 'children'. I've tried all sorts of different things; e.g. dropping the brackets and different formats, changed it to Child, different versions of this String solution, I also found this input type thing, but it felt like it wouldn't be compatible with multiple @hasMany table references.

I also found this solution of creating a child/leaf item while referencing the parent, but wasn't sure how to adapt it, since the syntax in the last code example "userToDoItemsOwner" seemed to come out of no where.

Would welcome any thoughts here; I'm feeling so stuck! :)

  • Play around with this in the AWS AppSync Console. It's faster as you can focus on the mutations before having to write it in the front-end. – Luke Feb 22 '23 at 09:09
  • Looking at the example [here](https://docs.amplify.aws/cli/graphql/data-modeling/#has-many-relationship), it looks like writing `user: User @belongsTo` may be obsolete. – Luke Feb 22 '23 at 09:11
  • Thanks! I will give it a go and report back – femmedecentral Feb 22 '23 at 15:51
  • @Luke I was able to identify the correct syntax after looking at AppSync Console (great!); it was userChildrenId to pass in when creating the Child. But I've run into a couple of issues: 1) When I look at the User object that is the parent to the newly created Child, I expected the Child to be listed under the children[] attribute, but I'm seeing nothing. 2) I'm trying to play around with AppSync console, but I'm getting an unauthorized access error on every query I try. Any pointers? Thanks so much. – femmedecentral Feb 22 '23 at 17:45
  • Sign into App Sync as the owner. You've set `[{allow: owner}])`, so you'll need to sign in and then do the queries. – Luke Feb 23 '23 at 18:57
  • Oh, interesting. I'll give that a shot. :) Thanks for the response! I'll summarize what I found in a answer in just a few. Really appreciate your help; I was really getting stuck. – femmedecentral Feb 24 '23 at 00:28

1 Answers1

1

I figured it out! I ended up modifying my schema a little to the following, dropping the @belongsTo per Luke's suggestion:

type User @model @auth(rules: [{allow: owner}]) {
  id: ID!
  email: String! @index(name: "byEmail", queryField: "userByEmail", sortKeyFields: ["id"])
  children: [Child] @hasMany
  settings: String
  lastInteraction: String
}

type Child @model @auth(rules: [{allow: owner}]) {
  id: ID!
  name: String!
  age: String!
  commonNoun: String!
  stories: [Story] @hasMany
}

type Story @model @auth(rules: [{allow: owner}]) {
  id: ID!
  status: String
  storyType: String
  storyData: String!
}

The correct pattern was to create the Child item while referencing the userID using "userChildrenId". This is what the creation code for a child object associated with the owning User item:

  async function createChildinDB(childAge, childName, childCommonNoun, userID) {
      
    const data = {
      age: childAge,
      name: childName,
      commonNoun: childCommonNoun,
      userChildrenId: userID
    };

    const childCreationResponse = await API.graphql({
      query: createChild,
      variables: { input: data },
      authMode: "AMAZON_COGNITO_USER_POOLS"
    }).then(response => {
      return response;
    }).catch(e => {
        console.log(e);
        return null;
    });

    return(childCreationResponse);

  }

With this pattern, I was also still able to successfully query for all children associated with a particular user:

export async function getChildrenforUser(userID) {

  // filter on only children of user
  const variablesForChildrenofUserQuery = {
    filter: {
      userChildrenId: {
        eq: userID
      }
    }

  }

  const getUserChildren = await API.graphql({
    query: listChildren,
    variables: variablesForChildrenofUserQuery,
    authMode: "AMAZON_COGNITO_USER_POOLS"
  }).then(response => {
    console.log(response);
    return response;
  }).catch(e => {
      console.log(e);
      return null;
  });

  return(getUserChildren);
}

Hope this helps someone else in the future :)

  • Great job! Testing new schema design in AWS AppSync is essential before writing the code in the front-end. – Luke Feb 24 '23 at 07:00