Auto-generated Mutation Does Not Create Relationship

Hello,

I want to test auto-generated CRUD operations created by calling makeAugmentedSchema. There is no problem with creating nodes but creating relationship does not work for me. Please advise on what I am doing wrong here.

Schema:

type Bio{
    id: ID!
    description: String
}

type Person{
    id: ID!
    name: String
    dob: Date
    gender: String
    bioRelation: [Bio] @relation(name: "HAS_BIO", direction: "OUT")
}

Mutation:

mutation {
 	p: CreatePerson(
    	name: "Anton",
    	gender: "Male") {
    name
    gender
    id
  }
 	b: CreateBio(
    	description: "I am a developer") {
    description
		id
  }
  r: AddPersonBioRelation(
    from: {id: "p"},
    to:{id: "b"}
  ){
    from{
      name
    }
    to{
      description
    }
  }
}

I am following the Interface Mutations guidance https://grandstack.io/docs/graphql-interface-union-types.

I get Person and Bio nodes created but no relationship gets created between the two:

{
  "data": {
    "p": {
      "name": "Anton",
      "gender": "Male",
      "id": "586b63fd-f9a5-4274-890f-26ba567c065c"
    },
    "b": {
      "description": "I am a developer",
      "id": "a46b4c22-d23b-4630-ac84-9d6248bdda89"
    },
    "r": null
  }
}

Thank you

This is how AddPersonBioRelation looks like:

You're not able to do the addRelationship mutation in conjunction with node creation mutations. You have to do is separately or create a custom cypher mutation that performs the operation in one go.

Thank you for helping, @MuddyBootsCode!

As I see, neo4j-graphql.js developers claim that it is possible to do the addRelationship mutation in conjunction with node creation mutations: https://grandstack.io/docs/graphql-interface-union-types.

I do want to reference newly created nodes by their id when creating relationship within one mutation. Could you please provide with example of such a custom cypher mutation that performs the operation in one go.

Many thanks,
Anton

Hmm that might be an updated part of the docs but to do a cyper mutation you'd just write a cyper statement that did what you're trying to do:

CREATE (a:node)
CREATE (b:node)
SET (a)-[:RELATIONSHIP]->(b)
return Whatever

@MuddyBootsCode, your answer looks like some sort of trolling, mate. The question is about how to reference newly created nodes along with creating the relationship in one go.

As I see there is an option to assign id at the stage of node creation and pass them to a relationship definition, but it raises the problem of managing the uniqueness of nodes as UUID gets overwritten.

I’m not sure if your level of experience with either Neo4j or the grand stack but you can write custom mutations using cypher. I’ve given you a template on how to do that. Not an exact answer. No trolling involved. https://grandstack.io/docs/graphql-schema-directives You’ll see with the @cypher directive you can do what you’re after.

@MuddyBootsCode, you gave an example of creating two generic nodes with a relationship, which is pretty obvious. What would be helpful is the example of a resolver that overwrites autogenerated mutation that connects two newly created nodes keeping their uuid's.

I reckon mutation should looks like this in schema definition:

type Mutation {
  AddPersonBio(fromPersonID: ID!, toBioID: ID!): Person @cypher(
  statement:"""
     MATCH (from:Person {id: $fromPersonID})
     MATCH (to:Bio {id: $toBioID})
     MERGE (from)-[:HAS_BIO]->(to)
     RETURN from.id, to.id
     """)

But how do I implement a resolver to be able to reference uuids of newly created objects?

Could you please provide with a relevant example addressing this question?

Thanks.

Ok. I think the issue is that you're missing a reciprocal piece of your relationship on your bio type. You also need to add it's relation to a person in your case it could look like:

type Bio {
   id: ID!
   description: String
   person: Person @relation(name: "HAS_BIO", direction: "IN")
}

When you're using relationships you have to specify both types and ensure the in and out directions are correct. Give that a try and let me know.

Sorry the other stuff before but in some of the larger GRANDstack projects I've built I've found it much easier to go the custom Cypher mutation route for most things, rather than trying to coerce the auto-generated mutations into what I was trying to do.

I have defined relation on Person node already (please see my first message). Building symmetric relations is considered as not the best solution as to Neo4j official guidance which I am agreed with:

@William_Lyon, could you please comment on the best solution for this problem?

Thank you.

You’re first post shows an error on your schema. I pointed it out to you. You’re missing the reciprocal relationship from Bio to Person.

@MuddyBootsCode, do I get it right, that you do not consider Person->Bio and Bio->Person relationships as symmetrical for this example?

There is no error in a schema re type definitinos in my first post, the confusion comes from mutation definition.

@MuddyBootsCode, it looks like you do not follow the point. I do not mind if you stop posting misleading messages.

In your first post you’ve posted your schema. In it you show to have no relationship from Bio to person. In order for makeAugmentedSchema to know how to create the correct relationship you need to include the relationship on both the person and bio type. With a correct in and out direction for the relationship. This does not create a separate relationship just ensures that it’s mapped correctly.

Try that and see if it fixes your mutation. If it doesn’t then holpefully someone else feels like helping you.

In the example in the docs you referenced the id values are provided to the create node mutations, which can then be used to reference the nodes in the add relationship mutation.

If no id value is provided to the node creation mutation then a UUID is generated, and this value must be provided to the add relationship mutation. However, you won't know what those values are, thus necessitating a second operation.

If however, you specify values for the id fields then this can be done in a single operation. So your example becomes:

mutation {
 	p: CreatePerson(
    	name: "Anton",
    	gender: "Male",
        id: "p1") {
    name
    gender
    id
  }
 	b: CreateBio(
    	description: "I am a developer",
        id: "b1") {
    description
		id
  }
  r: AddPersonBioRelation(
    from: {id: "p1"},
    to:{id: "b1"}
  ){
    from{
      name
    }
    to{
      description
    }
  }
}

Thank you, @William_Lyon. Now, I see that there is no way to pass generated UUID to add relationship mutation in the same operation where nodes get created.

I have come up with an idea of introducing a personId property to both Person and Bio nodes that I can use to connect the two and remove once relationship is created. This allows to perform the operation in one go.

type Bio{
    id: ID!
    personId: Int
    description: String
}

type Person{
    id: ID!
    personId: Int
    name: String
    gender: String
    bioRelation: [Bio] @neo4j_ignore
}

type Mutation{
    AddPersonBioRelation(personId: Int): Person
        @cypher(statement:
        """
        MERGE (p:Person {personId: $personId})
        MERGE (b:Bio {personId: $personId})
        CREATE (p)-[r:HAS_BIO]->(b)
        REMOVE p.personId, b.personId
        RETURN p
        """
        )
}

The operation looks like this:

Which creates nodes along with a relationship keeping nodes' UUIDs:

I am not sure if this is considered as a good practice, though. From one side, such implementation defines a property for Person and Bio nodes just to facilitate a creation operation, but not for describing an entity. From the other side, api gets more user friendly for clients to use.

Super-interesting comment! And something I’ve been wondering about. Makes me wonder if there’s a highly-opinionated article online about using GRANDstack that shared (empirical) perspectives like this.

My concern about custom mutations is that they need to be manually updated as the schema definition changes. Do you run tests to compare your custom mutation to a group of more granular (autogenerated) mutations?

The two approaches I tend to use are either chaining operations together in the application with hooks i.e. useQuery or use Mutation until I hit a point where I feel the operation is just cleaner or easier to perform using Cypher. That’s mostly a personal opinion on when that happens. I think it just hits a point where Cypher is easier.

That being said I make a lot of use of input types to ensure that things don’t break or are caught by tests when they do.

1 Like