Interface Types
The Neo4j GraphQL Library supports the use of interfaces on relationship fields. This chapter will walk through their definition and usage.
Type definitions
The following schema defines a Actor
type, that has a relationship ACTED_IN
, of type [Production!]!
. Production
is an interface type with Movie
and Series
implementations. Note in this example that relationship properties have also been used with the @relationshipProperties
directive as syntactic sugar, so that interfaces representing relationship properties can be easily distinguished.
interface Production {
title: String!
actors: [Actor!]!
}
type Movie implements Production {
title: String!
actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
runtime: Int!
}
type Series implements Production {
title: String!
actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
episodes: Int!
}
interface ActedIn @relationshipProperties {
role: String!
}
type Actor {
name: String!
actedIn: [Production!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn")
}
These type definitions will be used for the rest of the examples in this chapter.
Directive inheritance
Any directives present on an interface or its fields will be "inherited" by any object types implementing it. For example, the type definitions above could be refactored to have the @relationship
directive on the actors
field in the Production
interface instead of on each implementing type as it is currently:
interface Production {
title: String!
actors: [Actor!]! @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
}
type Movie implements Production {
title: String!
actors: [Actor!]!
runtime: Int!
}
type Series implements Production {
title: String!
actors: [Actor!]!
episodes: Int!
}
interface ActedIn @relationshipProperties {
role: String!
}
type Actor {
name: String!
actedIn: [Production!]! @relationship(type: "ACTED_IN", direction: OUT, properties: "ActedIn")
}
Overriding
In addition to inheritance, directives can be overridden on a per-implementation basis. Say you had an interface defining some Content
, with some basic authorization rules:
interface Content
@auth(rules: [{ operations: [CREATE, UPDATE, DELETE], allow: { author: { username: "$jwt.sub" } } }]) {
title: String!
author: [Author!]! @relationship(type: "HAS_CONTENT", direction: IN)
}
type User {
username: String!
content: [Content!]! @relationship(type: "HAS_CONTENT", direction: OUT)
}
You might implement this once for public content and once for private content which has additional rules in place:
type PublicContent implements Content {
title: String!
author: [Author!]!
}
type PrivateContent implements Content
@auth(rules: [{ operations: [CREATE, READ, UPDATE, DELETE], allow: { author: { username: "$jwt.sub" } } }]) {
title: String!
author: [Author!]!
}
The PublicContent
type will inherit the auth rules from the Content
interface, whilst the PrivateContent
type will use the auth rules specified there.
In summary, there are three choices for the application of directives when using interfaces:
-
Directives specified on the interface and inherited by all implementing types when the directives for every type are the same.
-
Directives specified on the interface and overridden by certain implementing types when directives are broadly the same with a few discrepancies.
-
Directives specified on implementing types alone when there is very little commonality between types, or certain types need a directive and others don’t.
Querying an interface
Which implementations are returned by a Query are dictated by the where
filter applied.
For example, the following will return all productions with title starting "The " for every actor:
query GetProductionsStartingWithThe {
actors {
name
actedIn(where: { node: { title_STARTS_WITH: "The " } }) {
title
... on Movie {
runtime
}
... on Series {
episodes
}
}
}
}
Whilst the query below will only return the movies with title starting with "The " for each actor.
query GetMoviesStartingWithThe {
actors {
name
actedIn(where: { node: { _on: { Movie: { title_STARTS_WITH: "The " } } } }) {
title
... on Movie {
runtime
}
}
}
}
This is to prevent overfetching, and you can find an explanation of this here.
Alternatively, these implementation specific filters can be used to override filtering for a specific implementation. For example, if you wanted all productions with title starting with "The ", but movies with title starting with "A ", you could achieve this using the following:
query GetProductionsStartingWith {
actors {
name
actedIn(where: { node: { title_STARTS_WITH: "The ", _on: { Movie: { title_STARTS_WITH: "A " } } } }) {
title
... on Movie {
runtime
}
... on Series {
episodes
}
}
}
}
Creating using an interface field
The below mutation creates an actor and some productions they’ve acted in:
mutation CreateActorAndProductions {
createActors(
input: [
{
name: "Chris Pratt"
actedIn: {
create: [
{
edge: {
role: "Mario"
}
node: {
Movie: {
title: "Super Mario Bros"
runtime: 90
}
}
}
{
edge: {
role: "Starlord"
}
node: {
Movie: {
title: "Guardians of the Galaxy"
runtime: 122
}
}
}
{
edge: {
role: "Andy"
}
node: {
Series: {
title: "Parks and Recreation"
episodes: 126
}
}
}
]
}
}
]
) {
actors {
name
actedIn {
title
}
}
}
}
Nested interface operations
Operations on interfaces are abstract until you instruct them not to be. Take the following example:
mutation CreateActorAndProductions {
updateActors(
where: { name: "Woody Harrelson" }
connect: {
actedIn: {
where: { node: { title: "Zombieland" } }
connect: { actors: { where: { node: { name: "Emma Stone" } } } }
}
}
) {
actors {
name
actedIn {
title
}
}
}
}
The above Mutation will:
-
Find any
Actor
nodes with the name "Woody Harrelson" -
Connect those nodes to any
Production
(Movie
orSeries
) nodes with the title "Zombieland" -
Connect the connected
Production
nodes to anyActor
nodes with the name "Emma Stone"
As you can see, this is abstract all the way down. If you wanted to only connect Movie
nodes to Actor
nodes with name "Emma Stone", you could instead do:
mutation CreateActorAndProductions {
updateActors(
where: { name: "Woody Harrelson" }
connect: {
actedIn: {
where: { node: { title: "Zombieland" } }
connect: { _on: { Movie: { actors: { where: { node: { name: "Emma Stone" } } } } } }
}
}
) {
actors {
name
actedIn {
title
}
}
}
}
Likewise, you could move this up a level to make sure you only connect to Movie
nodes with title "Zombieland":
mutation CreateActorAndProductions {
updateActors(
where: { name: "Woody Harrelson" }
connect: {
actedIn: {
where: { node: { _on: { Movie: { title: "Zombieland" } } } }
connect: { actors: { where: { node: { name: "Emma Stone" } } } }
}
}
) {
actors {
name
actedIn {
title
}
}
}
}
Was this page helpful?