GraphQL Relationships

To add relationships to a GraphQL API using the Neo4j GraphQL Library you use the @relationship GraphQL schema directive.

GraphQL schema directives are GraphQL’s built-in extension mechanism that allows developers to indicate some custom server-side logic should occur.

GraphQL schema directives are extremely powerful and are used to configure the GraphQL API generated by the Neo4j GraphQL Library. You will see more examples of using GraphQL schema directives in the next section of this course.

To add a relationship to the GraphQL API you need to take the following steps:

  1. Add a new field to represent the relationship

  2. Define the type of this field (what type is on the other end of the relationship?)

  3. Use the @relationship directive to define the relationship type (as stored in Neo4j) and the direction of the relationship (IN or OUT).

Let’s update the GraphQL type definitions to add the relationship between Movie, Actor and Genre nodes.

Start by updating the type definitions in the GraphQL Toolbox to capture the relationships present in the property graph model above i.e. IN_GENRE, ACTED_IN and RATED.

GraphQL
type Movie @node {
  ...
  actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN)
  genres: [Genre!]! @relationship(type: "IN_GENRE", direction: OUT)
}

type Actor @node {
  ...
  movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
}

type Genre @node {
  ...
  movies: [Movie!]! @relationship(type: "IN_GENRE", direction: IN)
}
Learn how to create GraphQL APIs using Neo4j GraphQL Toolbox and Library.Relationship fields are non-nullable

The relationship fields are defined as non-nullable (e.g. [Actor!]!) since if a relationship exists, it will always connect exactly two nodes, therefore, a relationship field can never be null.

Click to show the complete type definition
GraphQL
type Movie @node {
  title: String!
  year: Int
  plot: String
  imdbRating: Float
  countries: [String]
  languages: [String]
  poster: String
  revenue: Int
  budget: Int
  actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN)
  genres: [Genre!]! @relationship(type: "IN_GENRE", direction: OUT)
}

type User @node {
  userId: ID!
  name: String!
}

type Actor @node {
  name: String!
  movies: [Movie!]! @relationship(type: "ACTED_IN", direction: OUT)
}

type Genre @node {
  name: String!
  movies: [Movie!]! @relationship(type: "IN_GENRE", direction: IN)
}

Rebuild the schema to update the GraphQL schema and return to the query editor.

Finally, update the Matrix movie search query to traverse from the movie nodes to the actors and genres.

GraphQL
query MyQuery {
  movies(where: { title_CONTAINS: "Matrix" }) {
    title
    actors {
      name
    }
    genres {
      name
    }
  }
}

Run the query and note how the actors and genres connected to each Matrix movie are returned.

JSON
{
  "data": {
    "movies": [
      {
        "title": "Matrix Reloaded, The",
        "actors": [
          { "name": "Keanu Reeves" },
          { "name": "Carrie-Anne Moss" },
          { "name": "Christine Anu" },
          { "name": "Andy Arness" }
        ],
        "genres": [
          { "name": "IMAX" },
          { "name": "Thriller" },
          { "name": "Action" },
          { "name": "Sci-Fi" },
          { "name": "Adventure" }
        ]
      },
      {
        "title": "Matrix Revolutions, The",
        "actors": [
          { "name": "Keanu Reeves" },
          { "name": "Mary Alice" },
          { "name": "Helmut Bakaitis" },
          { "name": "Kate Beahan" }
        ],
        "genres": [
          { "name": "Action" },
          { "name": "IMAX" },
          { "name": "Thriller" },
          { "name": "Sci-Fi" },
          { "name": "Adventure" }
        ]
      },
      {
        "title": "Matrix, The",
        "actors": [
          { "name": "Hugo Weaving" },
          { "name": "Laurence Fishburne" },
          { "name": "Keanu Reeves" },
          { "name": "Carrie-Anne Moss" }
        ],
        "genres": [
          { "name": "Thriller" },
          { "name": "Sci-Fi" },
          { "name": "Action" }
        ]
      }
    ]
  }
}
Click to explore how the generated Cypher query traverses the relationships

The Neo4j GraphQL library generates the following Cypher query for the above GraphQL query:

cypher
MATCH (this:`Movie`)
WHERE this.title CONTAINS $param0
CALL {
    WITH this
    MATCH (this)<-[this0:ACTED_IN]-(this1:`Actor`)
    WITH this1 { .name } AS this1
    RETURN collect(this1) AS var2
}
CALL {
    WITH this
    MATCH (this)-[this3:IN_GENRE]->(this4:`Genre`)
    WITH this4 { .name } AS this4
    RETURN collect(this4) AS var5
}
RETURN this { .title, actors: var2, genres: var5 } AS this
Params:
{
  "param0": "Matrix"
}

Try and identify the following in the Cypher query above:

  • How the actors and genres fields are added to the Cypher query

  • What relationship types are used to traverse from the Movie nodes to the Actor and Genre nodes

  • How the actors and genres fields are returned in the Cypher query

Relationship properties

Relations in Neo4j can have properties e.g. the ACTED_IN relationship between an Actor and a Movie can have a roles property that describes the role the actor played in the movie.

To add relationship properties to the GraphQL API you need to:

  1. Add a type to the GraphQL schema that represents the roles relationship property:

    GraphQL
    type ActedIn @relationshipProperties {
        role: String
    }

    To find out more about interfaces in GraphQL see the GraphQL documentation.
  2. Modify the actors field in the Movie type to pass the ActedIn type to the properties parameter of @relationship directive.

    GraphQL
    type Movie @node {
      actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN)
    }
  3. Add the same ActedIn type to the movies field in the Actor type:

    GraphQL
    type Actor @node {
      movies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT)
    }
Click to show the complete type definition
GraphQL
type Movie @node {
  title: String!
  year: Int
  plot: String
  imdbRating: Float
  countries: [String]
  languages: [String]
  poster: String
  revenue: Int
  budget: Int
  actors: [Actor!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN)
  genres: [Genre!]! @relationship(type: "IN_GENRE", direction: OUT)
}

type User @node {
  userId: ID!
  name: String!
}

type Actor @node {
  name: String!
  movies: [Movie!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT)
}

type Genre @node {
  name: String!
  movies: [Movie!]! @relationship(type: "IN_GENRE", direction: IN)
}

type ActedIn @relationshipProperties {
    role: String
}

Connection fields generated for the relationship type e.g. the actorsConnection of movies field can be used to access the role property of the ACTED_IN relationship between an Actor and a Movie:

GraphQL
query MyQuery {
  movies(where: {title_EQ: "Matrix, The"}) {
    title
    actors {
      name
    }
    actorsConnection {
      edges {
        properties {
          role
        }
      }
    }
    genres {
      name
    }
  }
}

For more information on relationship properties, see the Neo4j GraphQL Library Relationship Properties documentation.

In the next lesson, your challenge is to add new relationships to the GraphQL type definitions.

Check Your Understanding

1. Relationships Schema Directive

What is the name of the GraphQL schema directive used to define relationships when using the Neo4j GraphQL Library?

Choose the correct answer.

  • @relation

  • @join

  • @relationship

  • @auth

Hint

GraphQL schema directives are GraphQL’s built-in extension mechanism and allow the API developer to add "annotations" on types and fields in the GraphQL type definitions.

Solution

The @relationship GraphQL schema directive is used to define relationships.

2. Relationship Fields

True or False - relationship fields should always be defined as non-nullable when using the Neo4j GraphQL Library.

  • ✓ True

  • ❏ False

Hint

Relationships must be two-way; if a relationship field is nullable, a relationship could connect to only one node.

Solution

True - Relationship fields should always be non-nullable because if a relationship exists, it will always connect exactly two nodes

Summary

In this lesson, you learned how to add relationships to the GraphQL type definitions. You used the @relationship directive to define the relationship type and direction. You also learned how to add relationship properties to the GraphQL API.