Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
391 lines (286 sloc) 9.7 KB
id title sidebar_label
neo4j-graphql-plugin
Neo4j-GraphQL Database Plugin
Neo4j Database Plugin

This is a GraphQL-Endpoint extension for Neo4j and is part of GRANDstack.

This readme assumes you are somewhat familiar with GraphQL and minimally with Cypher.

Based on your GraphQL schema, it translates GraphQL Queries and Mutations into Cypher statements and executes them on Neo4j.

It offers both an HTTP API, as well as, Neo4j Cypher Procedures to execute and manage your GraphQL API.

<iframe width="560" height="315" src="https://www.youtube.com/embed/J-J90uwugb4" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen></iframe>

Installation

Download and install Neo4j Desktop.

Neo4j Desktop provides a quick install button for neo4j-graphql.

After creating your database you can find it under "Manage" in the "Plugins" tab for a single click install.

Neo4j Desktop Neo4j-GraphQL plugin installation

Use with neo4j-graphql-cli

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

This tool

  1. launches a Neo4j Sandbox with your GraphQL schema
  2. provides the /graphql/ endpoint,
  3. a Neo4j server,
  4. an hosted GraphiQL for it.
npm install -g neo4j-graphql-cli
neo4j-graphql movies-schema.graphql

Quickstart

To generate some graph data in Neo4j just run :play movie graph in your Neo4j Browser.

GraphiQL

The best tool to use is GraphiQL the GraphQL UI. Get and install it.

Enter your GraphQL URL, like http://localhost:7474/graphql/ (note the trailing slash).

If your Neo4j Server runs with authentication enabled, add the appropriate Basic-Auth (base64 encoded) username:password header in the "Edit HTTP Headers" screen.

.Command to generate the Authorization header value.

echo "Basic $(echo -n "neo4j:<password>" | base64)"

Uploading a GraphQL Schema

Here is a small example schema for the movie data. Just a Movie with actors, and a Person with movies.

Simple properties are mapped directly while the relationships are mapped to fields movies and actors

Movies Schema

type Movie {
  title: String!
  released: Int
  actors: [Person] @relation(name: "ACTED_IN", direction: IN)
}
type Person {
  name: String!
  born: Int
  movies: [Movie] @relation(name: "ACTED_IN")
}

You can POST a GraphQL schema to the /graphql/idl/ endpoint or run the CALL graphql.idl('schema-text') procedure.

The payload is parsed and stored in Neo4j and used subsequently as the backing GraphQL schema for validating and executing queries.

CALL graphql.idl('
type Movie  {
  title: String!
  released: Int
  actors: [Person] @relation(name:"ACTED_IN",direction:IN)
}
type Person {
  name: String!
  born: Int
  movies: [Movie] @relation(name:"ACTED_IN")
}
')

You should then be able to see your schema in the Docs section of GraphiQL.

This also gives you auto-completion, validation and hints when writing queries.

To visualize your GraphQL schema in Neo4j Browser use: call graphql.schema().

Auto-Generated Query Types

From that schema, the plugin automatically generate Query Types for each of the declared types.

e.g. Movie(title,released,first,offset,_id,orderBy, filter): [User]

  • Each field of the entity is available as query argument, with an equality check (plural for list-contains)
  • We also provide a filter argument for more complex filtering with nested predicates, also for relation-fields (see graphcool docs)
  • For ordered results there is a orderBy argument
  • And first, offset allow for pagination

Now you can for instance run this query:

Simple query example

{
  Person(name: "Kevin Bacon") {
    name
    born
    movies {
      title
      released
      tagline
    }
  }
}

Advanced query example

query Nineties($released: Long, $letter: String)
{ Movie(released: $released,
        filter: {title_starts_with: $letter,
                 actors_some: { name_contains: $letter}}) {
    title
    released
    actors(first: 3) {
      name
      born
      movies(first: 1, orderBy: title_desc) {
        title
        released
      }
    }
  }
}

# query variables
{ "released":1995, "letter":"A"}

This query declares query name and parameters (first line), which are passed separately ("Query Parameters box") as JSON.

And get this result:

Auto-Generated Mutations

Additionally Mutations for each type are created, which return update statistics.

e.g. for the Movie type:

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

and for it's relationships:

  • addMovieActors(title: ID!, actors:[ID]!) : String
  • deleteMovieActors(title: ID!, actors:[ID]!) : String

Those mutations then allow you to create and update your data with GraphQL.

Single Mutation

mutation {
  createPerson(name: "Chadwick Boseman", born: 1977)
}

Mutation Result

{
  "data": {
    "createPerson": "Nodes created: 1\nProperties set: 2\nLabels added: 1\n"
  }
}

Several Mutations at once

mutation {
  pp: createMovie(title: "Black Panther", released: 2018)
  lw: createPerson(name: "Letitia Wright", born: 1993)
  cast: addMovieActors(
    title: "Black Panther"
    actors: ["Chadwick Boseman", "Letitia Wright"]
  )
}

If the same mutations is called multiple times, you need to use alias prefixes to avoid clashes in the returned data, which is keyed on mutation names.

You can use those mutations also to load data from CSV or JSON.

Directives

Directives like @directiveName(param:value) can be used to augment the schema with additional meta-information that we use for processing.

You have already seen the @relation(name:"ACTED_IN", direction:"IN") directive to map entity references to graph relationships.

The @cypher directive is a powerful way of declaring computed fields, query types and mutations with a Cypher statement.

For instance, directors

type Movie {
  ...
  directors: [Person] @cypher(statement:"MATCH (this)<-[:DIRECTED]-(d) RETURN d")
}

Register Top-Level Schema Types

schema {
  query: QueryType
  mutation: MutationType
}

A custom query

type QueryType {
  ...
  coActors(name:ID!): [Person] @cypher(statement:"MATCH (p:Person {name:$name})-[:ACTED_IN]->()<-[:ACTED_IN]-(co) RETURN distinct co")
}

A custom mutation

type MutationType {
  ...
  rateMovie(user:ID!, movie:ID!, rating:Int!): Int
  @cypher(statement:"MATCH (p:Person {name:$user}),(m:Movie {title:$movie}) MERGE (p)-[r:RATED]->(m) SET r.rating=$rating RETURN r.rating")
}

Full enhanced Schema

type Movie {
  title: String!
  released: Int
  actors: [Person] @relation(name: "ACTED_IN", direction: IN)
  directors: [Person]
    @cypher(statement: "MATCH (this)<-[:DIRECTED]-(d) RETURN d")
}
type Person {
  name: String!
  born: Int
  movies: [Movie] @relation(name: "ACTED_IN")
}
schema {
  query: QueryType
  mutation: MutationType
}
type QueryType {
  coActors(name: ID!): [Person]
    @cypher(
      statement: "MATCH (p:Person {name:$name})-[:ACTED_IN]->()<-[:ACTED_IN]-(co) RETURN distinct co"
    )
}
type MutationType {
  rateMovie(user: ID!, movie: ID!, rating: Int!): Int
    @cypher(
      statement: "MATCH (p:Person {name:$user}),(m:Movie {title:$movie}) MERGE (p)-[r:RATED]->(m) SET r.rating=$rating RETURN r.rating"
    )
}

Procedures

This library also comes with Cypher Procedures to execute GraphQL from within Neo4j.

Simple Procedure Query

CALL graphql.query('{ Person(born: 1961) { name, born } }')

Advanced Procedure Query with parameters and post-processing

WITH 'query ($year:Long,$limit:Int) { Movie(released: $year, first:$limit) { title, actors {name} } }' as query

CALL graphql.query(query,{year:1995,limit:5}) YIELD result

UNWIND result.Movie as movie
RETURN movie.title, [a IN movie.actors | a.name] as actors

Update with Mutation

CALL graphql.execute('mutation { createMovie(title:"The Shape of Water", released:2018)}')

Procedures

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

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

Resources