neo4j-graphql-js icon indicating copy to clipboard operation
neo4j-graphql-js copied to clipboard

How to write complex cypher queries/mutations?

Open roschaefer opened this issue 5 years ago • 2 comments

The API reference has three ways to customize the generated resolvers:

  • neo4jgraphql(object, params, context, resolveInfo, debug)
  • cypherQuery(params, context, resolveInfo)
  • cypherMutation(params, context, resolveInfo)

The first executes cypher while the last two return a cypher query as string. How can I customize the generated cypher query/mutation, e.g. to create relationships along the way?

Use-case: I want to create an authorship relationship for every created post (referential integrity). The author must be the currently authenticated user. Therefore, the user comes from the context not the resolveInfo. Here's what I have so far:

CreatePost: async (object, params, ctx, resolveInfo) => {
const result = await neo4jgraphql(object, params, ctx, resolveInfo, false)

const session = ctx.driver.session()
await session.run(
   'MATCH (author:User {id: $userId}), (post:Post {id: $postId}) ' +
   'MERGE (post)<-[:WROTE]-(author) ' +
   'RETURN author', {
userId: ctx.user.id,
postId: result.id
})
session.close()

return result
}

There are a couple of problems with the above code:

  1. Non-atomic, no database transation
  2. The author is not in the result set
  3. The id of created post must be in resolveInfo

My idea is to have more control over the generated cypher query. Could you expose and document more methods, probably from this file how I can construct my own complex queries and mutations?

I could workaround the above problems like patching the resolveInfo object to access the generated id:

  // Keeping the graphql resolveInfo untouched ensures that we don't add the
  // following attributes to the result set returned to the graphQL client.
  // We only want to pass these attributes to our resolver for internal
  // purposes.
  const copy = cloneDeep(resolveInfo)

  copy.fieldNodes[0].selectionSet.selections.unshift({
    kind: 'Field',
    name: { kind: 'Name', value: 'id' }
  })
  return resolve(root, args, context, copy)

I'm excited about the new cypherParams feature in version 2.4.0 but I don't think it would help me for this particular problem as I have the user's id already.

An abstraction layer for Neo4J would be cool, e.g. for Rails there is ActiveRecord and Arel which helps you to compose complex SQL queries.

Here is our schema.graphql

Thank you for your great work so far

roschaefer avatar Feb 25 '19 17:02 roschaefer

I use the trick of adding a custom Mutation to do what I'm after:

CreateChildObjective(name: String!, parentId: ID!): Objective
      @cypher(
        statement: """
        MATCH (p:Objective { id: $parentId })
        MERGE (o:Objective { id: apoc.create.uuid(), name: $name})-[:DESCRIBES]->(p)
        RETURN o
        """
      )

Here, the lib gives me all the Mutations to "Create an Objective" and "Create the parent/child relationship between Objectives", but by create a custom mutation, I get more control and I can do everything I want all in one step.

mmmoli avatar Aug 05 '19 18:08 mmmoli

https://github.com/neo4j-graphql/neo4j-graphql-js/issues/608

michaeldgraham avatar May 02 '21 04:05 michaeldgraham