Add relational fields to create mutations?

Hey guys, playing with Grandstack and loving it. Bit new to GraphQL and neo4j, so sorry if this is a stupid question, but would appreciate some help. I have a few object types in my schema with relationships. I can create them just fine and link them later too via the API. So far, so good What I'd like to do now is pass in the ID of an existing object to the relational field of of a related object when creating that related object, but GrandStack doesn't appear to create relational fields on the create mutations. Is there a way to override that globally or could someone point me to any guidance on how to do this for a given create method? Thanks!

I'm not sure I understand what you mean here by relational fields.

Can you post a snippet of your typedefs as an example, along with what kind of query you want to do that isn't created?

Hi David,

Thanks for the quick response. Sorry if I'm using the wrong terminology. Still learning the Neo4j ecosystem and nomenclature.

Here's the schema of the Security object type I want to access via GraphQl:

type Security {
  id: ID!
  class: String
  series: String
  authorized: Int
  authorized_date: Date
  notes: String
  company: Company @ relation(name: "ISSUED_BY", direction:"OUT")
  user: User @relation(name: "CREATED", direction: "IN")
}

I want to create a new Security and pass in the id of the Company the security was "ISSUED_BY" as an argument when creating a new Security. The autogenerated mutations from Grandstack don't appear to let me do this.

I try to pass in this query:

mutation {
  CreateSecurity(authorized:100000
    series:"Series A"
    class:"Preferred"
    company:{id:"e48b2eb6-48e9-4e38-85e2-4ad198483568"}) {
    id
  }
}

And GraphQl throws an error that ""Unknown argument "company" on field "CreateSecurity" of type "Mutation".""

When I look at the schema provided at my API URL, I see there is no "company" field in the CreateSecurity mutation:

image

I'm sure I am simply missing something obvious, but I feel like that shouldn't be the case? At the very least, is there a way to quickly override this behavior? Like I said, I've bypassed the issue by writing my own query using Cypher that works, but ideally this is something I can get out of the box without having to write custom queries and resolvers? That was a big part of the appeal of Grandstack for me (which is awesome, btw).

I understand. Yes when you define your security type, it does not automatically generate mutators for other items. In order to do this, you have two options.

First, you could add a relationship type as described on this page, and then do your mutation in two steps - one to create JUST a security node, and then another to create the relationship from the security node to the company:

(See the section on relationship types)

The other option is that you can write your own custom mutation (like what you've specified here with CreateSecurityWithCompany) and you can then implement the "resolver" function in javascript to back that. See these docs:

In your JS when you get to the "makeAugmentedSchema" step you'd have to have a resolver function to go with that.

What you're asking for can't be automatically generated for a lot of reasons:

  • When you specify the company ID in CreateSecurity, it isn't clear if you intend this to be MERGE'd or MATCH'd in Neo4j
  • It isn't clear whether there might be more than one relationship type, or that the other end of the relation would be a Company.
3 Likes

Never thanked you for this. Much appreciated, David.

1 Like

Is this still the current thinking on the matter? It seems like such a common use case that it would be worth finding a way to accomplish without having to go outside the API provided by graphql-neo4j-js. After writing a growing number of such custom resolvers to accomplish this otherwise simple task, it gets quite repetitive, not to mention the addition of a whole outgrowth of code that a team must then review / maintain / etc...

Additionally, I'm not sure why you state that there's a lack of clarity regarding the relationship types. This is true from the perspective of Neo4j, but it's not true when given the additional information of a GraphQL schema. The schema states the valid node type(s) to which such a relation may connect, as well as the relation type, and therefore tightly constrains the node / relation types searched for when creating the desired "relation-node".

Furthermore, given that graphql-neo4j-js is functionally a DSL over GraphQL that maps surjectively (not injectively or bijectively) onto Neo4j queries, it doesn't seem far-fetched to generate Mutation types with the sole purpose of CREATEing related nodes as specified by the GraphQL schema. That is to say, MATCH the specified existing node (here Security), then CREATE a new node of the related type as well as the relation pattern, using the already bound nodes.

So, in the above example, the mutations would be something like CreateSecurityCompany and CreateSecurityUser, and their type signature would essentially look like those of CreateCompany and CreateUser, except that they would require the provision of a securityId. Given the directive-amended GraphQL schema, the from/to node types, relationship type, and relationship direction would seem to be deterministic and thus readily generatable.

Finally, from a best practices standpoint, it would make sense that an API that generates mutations called directly from the client provide the capacity to execute this kind of node "appendage" as a single transaction, which cuts against the first solution provided above, given that requiring two separate requests increases the risk that an app will end up with floating nodes due either to network issues or developer-land bugs. Of course a developer should implement their own logic ensuring that such considerations are taken into account and either rolled back or retried, but given that the present neo4j-graphql-js API forces such behavior for every simple node appendage, the temptation to get lazy grows and grows...

Perhaps I'm missing something here, and I'm certainly open to more elegant solutions, but as a user of the API this has been a continuous pain point, and I'm wondering if–with the passage of time–your thoughts have evolved around this topic.