Note
|
This is an very early version of GraphQL support for Neo4j. |
This implementation provides a GraphQL API to Neo4j, it comes as library but can also be installed as Neo4j server extension to act as a GraphQL endpoint. It turns GraphQL queries and mutations into Cypher statements and executes them on Neo4j.
We want to explore three approaches:
-
read schema / metadata from the database provide GraphQL DataFetcher that generate and run Cypher (WIP) √
-
make the same work with externally configured schema information (using IDL) √
-
do a direct GraphQL AST to Cypher transformation without bothering with Schema (TBD)
You can post GraphQL IDL schema to the /graphql/idl
endpoint which is parsed and stored in the graph.
That schema is then used as graphql-schema for validating and executing queries.
Also mutations for each type are created, which return the executed graph operations. e.g.
-
createMovie(title: String!, released: Int, tagline: String)
and -
addMoviePersons(title: String!, persons:[String]!)
Those mutations then allow you to create graph data with GraphQL.
The library samples the existing data and adds one GraphQLObject for each Node-Label with all the properties and their types found as outputs and input-arguments.
Relationship information is collected with direction, type, end-node-labels and degree (to determine single element or collection result).
Each relationship-type virtual property to the node type, named aType
for A_TYPE
.
The current version of this extension works with Neo4j 3.0 and 3.1
git clone https://github.com/neo4j-contrib/neo4j-graphql cd neo4j-graphql mvn clean package cp target/neo4j-graphql-0.1-SNAPSHOT.jar $NEO4J_HOME/plugins echo 'dbms.unmanaged_extension_classes=org.neo4j.graphql=/graphql' >> $NEO4J_HOME/config/neo4j.conf $NEO4J_HOME/bin/neo4j restart
name | information | example |
---|---|---|
entities |
each node label represented as entity |
|
multi entities |
multiple entities per query turned into |
|
properties (out) |
via sampling property names and types are determined |
|
field parameters |
all properties can be used as filtering (exact/list) input parameters, will be turned into Cypher parameters |
|
query parameters |
passed through as Cypher parameters |
|
relationships |
via a |
|
ordering |
via an extra |
|
pagination |
via |
|
schema first IDL support |
define schema via IDL |
|
Mutations |
create/delete mutations inferred from the schema |
|
Cypher queries |
|
|
Cypher updates |
Custom mutations by executing |
|
extensions |
extra information returned |
|
Run :play movies
in your Neo4j Server, click the statement and run it to insert the basic movie graph.
Get GraphiQL electron app from: https://github.com/skevy/graphiql-app
If your Neo4j Server runs with auth enabled, add the appropriate Basic-Auth header in the "Edit HTTP Headers" screen.
echo "Authorization: Basic $(echo -n "neo4j:<password>" | base64)"
And then run a query like:
query AllPeopleQuery { Person(name:"Kevin Bacon") { name born actedIn { title released tagline } } }
You can also use variables or query the schema:
{ __schema { types { name kind description } } }
or
{ __schema { queryType { fields { name, description } } } }
and then query for real data
# query query PersonQuery($name: String!) { Person(name: $name) { name born actedIn { title released tagline } } } # variables {"name":"Keanu Reeves"}
This library also comes with a User Defined Procedure to execute GraphQL:
WITH '{ Person(born: 1961) { name, born } }' as query, {} as params
CALL graphql.execute(query,params) YIELD result
UNWIND result.Person as p
RETURN p.name, p.born
You can also visualize your GraphQL schema in Neo4j Browser via a procedure.
CALL graphql.schema()
Some more examples
query MoviePersonQuery { Movie { title actedIn(name:"Tom Hanks") { name } } }
query PersonMoviePersonQuery { Person { name actedIn { title actedIn { name } } } }
query PersonQuery { Person(orderBy: [age_asc, name_desc]) { name born } }
(Optional if no data in database)
curl -u neo4j:<password> -i -XPOST -d'type Person { name: String, born: Int }' http://localhost:7474/graphql/idl/ {Person=MetaData{type='Person', ids=[], indexed=[], properties={name=PropertyType(name=String, array=false, nonNull=false), born=PropertyType(name=Int, array=false, nonNull=false)}, labels=[], relationships={}}}
curl -u neo4j:<password> -i -XPOST -d'{"query": "query {__schema {types {kind, name, description}}}"}' -H accept:application/json -H content-type:application/json http://localhost:7474/graphql/ {"data":{"__schema":{"types":[{"kind":"OBJECT","name":"QueryType","description":null},{"kind":"OBJECT","name":"Movie","description":"Movie-Node"},....
query {__schema {queryType { kind,description,fields { name } }}}
curl -u neo4j:<password> -i -XPOST -d'{"query": "query AllPeopleQuery { Person {name,born} } }"}' -H accept:application/json -H content-type:application/json http://localhost:7474/graphql/ HTTP/1.1 200 OK Date: Mon, 24 Oct 2016 21:40:15 GMT Content-Type: application/json Access-Control-Allow-Origin: * Transfer-Encoding: chunked Server: Jetty(9.2.9.v20150224) {"data":{"Person":[{"name":"Michael Sheen","born":1969},{"name":"Jack Nicholson","born":1937},{"name":"Nathan Lane","born":1956},{"name":"Philip Seymour Hoffman","born":1967},{"name":"Noah Wyle","born":1971},{"name":"Rosie O'Donnell","born":1962},{"name":"Greg Kinnear","born":1963},{"name":"Susan Sarandon","born":1946},{"name":"Takeshi Kitano","born":1947},{"name":"Gary Sinise","born":1955},{"name":"John Goodman","born":1960},{"name":"Christina Ricci","born":1980},{"name":"Jay Mohr","born":1970},{"name":"Ben Miles","born":1967},{"name":"Carrie Fisher","born":1956},{"name":"Christopher Guest","born":1948},{"name ...
curl -u neo4j:<password> -i -XPOST -d'{"query":"query PersonQuery($name:String!) { Person(name:$name) {name,born} }", "variables":{"name":"Kevin Bacon"}}' -H content-type:application/json http://localhost:7474/graphql/ HTTP/1.1 200 OK Date: Mon, 24 Oct 2016 21:40:38 GMT Content-Type: application/json Access-Control-Allow-Origin: * Transfer-Encoding: chunked Server: Jetty(9.2.9.v20150224) {"data":{"Person":[{"name":"Kevin Bacon","born":1958}]}}
curl -u neo4j:<password> -i -XPOST -d'{"query":"query PersonQuery { Person(name:\"Tom Hanks\") {name, born, actedIn {title, released} } }"}' -H content-type:application/json http://localhost:7474/graphql/ HTTP/1.1 200 OK Date: Tue, 25 Oct 2016 03:17:08 GMT Content-Type: application/json Access-Control-Allow-Origin: * Transfer-Encoding: chunked Server: Jetty(9.2.9.v20150224) {"data":{"Person":[{"name":"Tom Hanks","born":1956,"actedIn":[{"title":"Charlie Wilson's War","released":2007},{"title":"A League of Their Own","released":1992},{"title":"The Polar Express","released":2004},{"title":"The Green Mile","released":1999},{"title":"Cast Away","released":2000},{"title":"Apollo 13","released":1995},{"title":"The Da Vinci Code","released":2006},{"title":"Cloud Atlas","released":2012},{"title":"Joe Versus the Volcano","released":1990},{"title":"Sleepless in Seattle","released":1993},{"title":"You've Got Mail","released":1998},{"title":"That Thing You Do","released":1996}]}]}}
curl -X POST http://localhost:7474/graphql/idl -d 'type Person { name: String! born: Int movies: [Movie] @out(name:"ACTED_IN") totalMoviesCount: Int @cypher(statement: "WITH {this} AS this MATCH (this)-[:ACTED_IN]->() RETURN count(*) AS totalMoviesCount") recommendedColleagues: [Person] @cypher(statement: "WITH {this} AS this MATCH (this)-[:ACTED_IN]->()<-[:ACTED_IN]-(other) RETURN other") } type Movie { title: String! released: Int tagline: String actors: [Person] @in(name:"ACTED_IN") }' -u neo4j:****
call graphql.execute("query { Person { name born totalMoviesCount recommendedColleagues { name } }}", {}) yield result
UNWIND result.Person AS person
RETURN person.name, person.born, person.totalMoviesCount, [p IN person.recommendedColleagues | p.name]
LIMIT 10
╒══════════════╤═════════════╤═════════════════════════╤══════════════════════════════╕ │"person.name" │"person.born"│"person.totalMoviesCount"│"colleagues" │ ╞══════════════╪═════════════╪═════════════════════════╪══════════════════════════════╡ │"Keanu Reeves"│"1964" │"7" │["Diane Keaton","Jack Nicholso│ │ │ │ │n","Dina Meyer","Ice-T","Takes│ │ │ │ │hi Kitano","Brooke Langton","G│ │ │ │ │ene Hackman","Orlando Jones","│ │ │ │ │Al Pacino","Charlize Theron","│ │ │ │ │Hugo Weaving","Carrie-Anne Mos│ │ │ │ │s","Laurence Fishburne","Hugo │ │ │ │ │Weaving","Laurence Fishburne",│ │ │ │ │"Carrie-Anne Moss","Emil Eifre│ │ │ │ │m","Hugo Weaving","Laurence Fi│ │ │ │ │shburne","Carrie-Anne Moss"] │ └──────────────┴─────────────┴─────────────────────────┴──────────────────────────────┘
-
GraphQL-Java which we use in this project
-
GraphQL for Postgres as an inspiration of schema → native queries
-
GraphQL inspired Cypher features Map projections and Pattern comprehensions
-
Non-Null and Nullable Input and Output Types
-
Pagination: Skip and Limit (first,last,after,before,skip,limit)
-
√ orderBy with enum _PersonOrdering { name_asc,name_desc,… }
-
Filtering with support of a object argument for an input-argument-field, with key=comparator, and value compare-value
(status: {eq/neq:true}, createdAt: { gte: "2016-01-01", lt: "2016-02-01"}, tags: {isNull:false, includes/excludes: "foo"})
-
Handle result aggregation.
-
How to handle Geospatial and other complex input types
-
√ Support for Directives, e.g. to specify the cypher compiler or runtime? or special handling for certain fields or types
-
√ Add
extensions
result value for query statistics or query plan, depending on directives given, e.g. contain the generated cypher query as well -
@skip, @include directives, check if they are handled by the library
-
√ handle nested relationships as optional or non-optional (perhaps via nullable?) or directive
-
√ project non-found nested results as null vs. map with null-value entries
-
Connection add support for edges / nodes special properties
-
√ Support 3.1 via pattern comprehensions and map projections
-
Improvements: consider replacing MetaData with GraphQL types,
-
check if there is a direct conversion from parsed data (AST-Nodes) to graphql-schema types