neo4j-graphql / neo4j-graphql-js

NOTE: This project is no longer actively maintained. Please consider using the official Neo4j GraphQL Library (linked in README).
Other
609 stars 147 forks source link

Support directives on non-root fields #241

Open a-type opened 5 years ago

a-type commented 5 years ago

As far as I can tell currently, the library fails when you try to add neo4jgraphql as an 'entry point' anywhere except a field within the root Query or Mutation. For me, this generally looks like this:

type Document { #stored in NoSQL database
  id: ID!
  text: String!
  author: User! @cypher(statement: "MATCH (user:User{id:$authorId}) RETURN user")
}
const resolvers = {
  Document: {
    author: (parent, args, ctx, info) => {
      // 'work around' neo4jgraphql to provide 'virtual' arg
      return neo4jgraphql(parent, { authorId: parent.authorId }, ctx, info);
    }
  }
};

And results in an error like so:

GraphQLError: Cannot read property 'astNode' of undefined
      Path: foo,bar, Pos: 151
      Original Error:
      TypeError: Cannot read property 'astNode' of undefined
    TypeError: Cannot read property 'astNode' of undefined
    at getQueryArguments (...\node_modules\neo4j-graphql-js\dist\utils.js:462:78)
    at translateQuery (...\node_modules\neo4j-graphql-js\dist\translate.js:353:48)
    at cypherQuery (...\node_modules\neo4j-graphql-js\dist\index.js:134:40)
    at Object._callee$ (...\node_modules\neo4j-graphql-js\dist\index.js:45:31)
    ...

It appears that getQueryArguments assumes that the field being resolved is a part of Query (if it's a query operation) or Mutation (if a mutation). Thus it looks for field bar on Query instead of the return type of foo.

With the resolve info given, the library should be able to determine the type which contains the field and get the arguments there. But there are probably other assumptions made within the library around this same theme would would require fixing as well.

The advantage of being able to attach neo4jgraphql at different 'entry point' fields in different types is that it will play nicer with non-Neo4j data sources. I use a NoSQL database to store some document-style data which isn't connected and doesn't fit well in a graph model, but those documents store foreign key IDs for parts of the graph. I want to be able to seamlessly join my NoSQL-fetched data into a full Neo4j resolved query:

query Mixed {
  document { # stored in NoSQL database
    id
    text
    author { # stored in Neo4j
      id
      name
      friends {
        id
        name
      }
    }
  }
}

Today this is not possible with this library and somewhat awkward to do without it.

robmurtagh commented 4 years ago

This seems like an important feature when it comes to integrating into the GraphQL ecosystem. Is there any prospect of it being supported?

a-type commented 4 years ago

If there's any interest, I ended up supporting this feature somewhat in my own GraphQL-Cypher translation library: https://github.com/a-type/graphql-cypher

However, I stopped using Neo4J, so it's kind of dead. The code is available if anyone would find it useful to fork or otherwise read of course.

robmurtagh commented 4 years ago

@a-type Wow, that's really cool. Very impressive project! Thanks so much for pointing me to it.

I'll definitely take a more detailed look. I'm in the early days of exploring GraphQL <-> Neo4J connections. Just so I understand, what would be the advantages of writing cypher in graphql-cypher directives, as opposed to in regular resolvers? graphql-cypher does some query optimisation behind the scenes that wouldn't be possible in resolvers? And it obviously cuts out a lot of boiler plate I guess?

My original hope was to autogenerate a GraphQL interface to a Neo4J graph (nested on a non-root field of my schema) and then Federate it into another bigger GraphQL API. But that isn't possible because of Issue https://github.com/neo4j-graphql/neo4j-graphql-js/issues/260, and this Issue stops a possible workaround.

I wonder how graphql-cypher would play with Federation...

a-type commented 4 years ago

@robmurtagh The advantage for both neo4j-graphql-js and graphql-cypher is the ability to translate deeply nested GraphQL queries to Cypher efficiently to reduce the total number of DB round-trips.

For instance, say a User has 200 friends.

With traditional resolvers, you would make one query for the User, then for each of the 200 friend edges you'd have to make another query (201 queries total). Additionally, you have to re-query the original user to traverse its relationship to each friend.

Using simple optimizations like Facebook's DataLoader, you could reduce that to 2 queries by batching all the friend queries together, but this still scales upward as you traverse deeper (friends-of-friends). The re-querying of the original user still applies as well.

With tools like graphql-cypher, we actually translate the entire GraphQL query into a native Cypher query, so you make 1 query, no matter how deep you go. Since the single query still has the parent user in memory, there's no need to re-query it.

I've never used federation so I can't guarantee support for it. If it works, cool; if not, you're welcome to open a PR.

robmurtagh commented 4 years ago

Brilliant, makes sense, thanks so much

michaeldgraham commented 3 years ago

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