GraphQL bindings for Neo4j, generates and runs Cypher
Kotlin Java
Clone or download
Pull request Compare This branch is even with neo4j-graphql:master.
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
docs/img
src
.gitignore
.travis.yml
license.txt
movies.graphql
movies.schema
pom.xml
readme.adoc

readme.adoc

Neo4j-GraphQL Extension

Note
This branch is for supporting Neo4j 3.1.

Build Status

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:

  1. read schema / metadata from the database provide GraphQL DataFetcher that generate and run Cypher (WIP) √

  2. make the same work with externally configured schema information (using IDL) √

  3. do a direct GraphQL AST to Cypher transformation without bothering with Schema (TBD)

License

Apache License v2

Installation

The extension works with Neo4j 3.1 and 3.2 the code on this branch is for 3.2.

Using with neo4j-graphql-cli

This extension is utilized when you use neo4j-graphql-cli

The script spawns a Neo4j sandbox with your schema and provides the /graphql/ endpoint, a Neo4j server and an hosted GraphiQL for it.

npm install -g neo4j-graphql-cli
neo4j-graphql movies-schema.graphql

Building manually

git clone https://github.com/neo4j-contrib/neo4j-graphql
cd neo4j-graphql
git checkout 3.2
mvn clean package
cp target/neo4j-graphql-*.jar $NEO4J_HOME/plugins
echo 'dbms.unmanaged_extension_classes=org.neo4j.graphql=/graphql' >> $NEO4J_HOME/conf/neo4j.conf
$NEO4J_HOME/bin/neo4j restart

GraphQL Schema

Uploading a Schema

You can post GraphQL IDL schema to the /graphql/idl endpoint or run the `graphql.idl('schema') procedure. The schema is parsed and stored in the graph. That schema is then used as GraphQL schema for validating and executing queries.

We automatically generate query types for each of the types.

  • e.g. User(name,age,first,offset,_id,sort): [User]

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.

Schema from Graph

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.

Usage

Run :play movies in your Neo4j Server, click the statement and run it to insert the basic movie graph.

GraphiQL

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
    }
  }
}
graphiql

You can also use variables or query the schema:

Which types are in the schema
{
  __schema {
    types {
      name
      kind
      description
    }
  }
}

or

Which types are available for querying
{
  __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"}

Procedure

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
graphql.execute

You can also visualize your GraphQL schema in Neo4j Browser via a procedure.

CALL graphql.schema()
graphql.schema

And you can even visualize remote graphql schemas, e.g. here from the GitHub GraphQL API. Make sure to generate the Personal Access Token to use in your account settings.

call graphql.introspect("https://api.github.com/graphql",{Authorization:"bearer d8xxxxxxxxxxxxxxxxxxxxxxx"})

graphql.introspect github

Examples

Some more examples

Relationship Argument
query MoviePersonQuery {
  Movie {
    title
    actedIn(name:"Tom Hanks") {
      name
    }
  }
}
Nested Relationships
query PersonMoviePersonQuery {
  Person {
    name
    actedIn {
      title
      actedIn {
        name
      }
    }
  }
}
Sorting
query PersonQuery {
  Person(orderBy: [age_asc, name_desc]) {
    name
    born
  }
}

CURL

POST IDL

(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={}}}

Query the Schema

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
  }
}}}

Get All People

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
...

Get one Person by name with Parameter

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}]}]}}

Schema first

curl -X POST http://localhost:7474/graphql/idl -d 'type Person {
            name: String!
            born: Int
            movies: [Movie] @relation(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] @relation(name:"ACTED_IN",direction: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"]  │
└──────────────┴─────────────┴─────────────────────────┴──────────────────────────────┘

Features

name information example

entities

each node label represented as entity

{ Person {name,born} }

multi entities

multiple entities per query turned into UNION

{ Person {name,born} Movie {title,released} }

properties (out)

via sampling property names and types are determined

{ Movie {title, released} }

field parameters

all properties can be used as filtering (exact/list) input parameters, will be turned into Cypher parameters

{ Movie(title:"The Matrix") {name,released} }

query parameters

passed through as Cypher parameters

query MovieByParameter ($title: String!) { Person(name:$name) {name,born} }

relationships

via a @relationship annotated field, optional direction

type Person { name: String, movies : Movie @relation(name:"ACTED_IN", direction:OUT) }

ordering

via an extra orderBy parameter

query PersonSortQuery { Person(orderBy:[name_desc,born_desc]) {name,born}}

pagination

via first and offset parameters

query PagedPeople { Person(first:10, offset:20) {name,born}}

schema first IDL support

define schema via IDL

:POST /graphql/idl "type Person {name: String!, born: Int}"

Mutations

create/delete mutations inferred from the schema

createMovie(title:ID!, released:Int) updateMovie(title:ID!, released:Int) deleteMovie(title:ID!)

createMoviePersons(title:ID!,persons:[ID!]) deleteMoviePersons(title:ID!,persons:[ID!])

Cypher queries

@cypher directive on fields and types, parameter support

actors : Int @cypher(statement:"RETURN sizethis)←[:ACTED_IN]-(")

Cypher updates

Custom mutations by executing @cypher directives

createPerson(name: String) : Person @cypher(statement:"CREATE (p:Person {name:{name}}) RETURN p")

extensions

extra information returned

fields are: columns, query, warnings, plan, type READ_ONLY/READ_WRITE,

Note
@cypher directives can have a passThrough:true argument, that gives sole responsibility for the nested query result for this field to your Cypher query. You will have to provide all data/structure required by client queries. Otherwise, we assume if you return object-types that you will return the appropriate nodes from your statement.

TODO

  • 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