Interface types

This is the documentation of the GraphQL Library version 7. For the long-term support (LTS) version 5, refer to GraphQL Library version 5 LTS.

This page describes how to use interfaces in the context of relationships, and exemplifies how to use them in queries and mutations.

Data model

Consider the following graph where a Person node has two relationships of the same name ACTED_IN, but connecting to both a Movie and a Series node. The ACTED_IN relationship has different properties depending on the target node type.

relationship interface
Figure 1. A Person node connected to Movie and Series nodes via the ACTED_IN relationship, with different properties

Consider the following data:

Person 2Movie 2Series ActedIn
Figure 2. Sample data following the data model

Relationships on an interface

The Movie and Series types are quite similar. What they have in common can be captured by defining an interface Production, implemented by both Movie and Series, and declaring the relationship field actors on that interface.

This allows you to query the relationship from the interface, while supporting the flexibility of relationship details such as type and properties to be defined in the concrete types.

@declareRelationship

The @relationship directive can only be used on fields of object types.

If you want to model relationships on interfaces, which are then implemented by concrete object types, you can use the @declareRelationship directive.

Declaring a relationship field on an interface
interface Production {
  title: String!
  actors: [Person!]! @declareRelationship
}

type Person @node {
  name: String!
}

Implementing the declared relationship

The concrete types implementing the Production interface must then define their own relationship details such as type and properties. This captures their nuances, while still being queryable as a field of the Production interface.

Notice how the ACTED_IN relationship differs between ActedInSeries and ActedIn.

Extending the type definitions with Movie and Series which implement the Production Interface
interface Production {
  title: String!
  actors: [Person!]! @declareRelationship
}

type Movie implements Production @node {
  title: String!
  released: Int!
  actors: [Person!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: IN)
}

type Series implements Production @node {
  title: String!
  episodes: Int!
  actors: [Person!]! @relationship(type: "ACTED_IN", properties: "ActedInSeries", direction: IN)
}

type ActedIn @relationshipProperties {
  roles: [String!]!
}

type ActedInSeries @relationshipProperties {
  roles: [String!]!
  episodes: [Int!]!
}

type Person @node {
  name: String!
}

A declared relationship must be implemented in all types that implement the interface, just like any other field on an interface type. For instance, the actors field must be implemented in both the Movie and Series types, and decorated with the @relationship directive.

Querying an interface relationship

When querying a relationship field declared on an interface, while the relationship field is accessible from the interface level, the returned data is based on the concrete type that implements the interface.

The generated query fields for Production, Movie and Series
# Top-level interface query fields
productions(limit: Int, offset: Int, sort: [ProductionSort!], where: ProductionWhere): [Production!]!
productionsConnection(after: String, first: Int, sort: [ProductionSort!], where: ProductionWhere): ProductionsConnection!
# Top-level concrete type query fields
movies(limit: Int, offset: Int, sort: [MovieSort!], where: MovieWhere): [Movie!]!
moviesConnection(after: String, first: Int, sort: [MovieSort!], where: MovieWhere): MoviesConnection!
series(limit: Int, offset: Int, sort: [SeriesSort!], where: SeriesWhere): [Series!]!
seriesConnection(after: String, first: Int, sort: [SeriesSort!], where: SeriesWhere): SeriesConnection!
Example 1. Querying Production nodes with related Actor nodes

This query returns a sorted list of up to 10 productions, that is a mix of Movie and Series nodes, with their related actors.

Notice the use of inline fragments to access the fields specific to the concrete types.

query {
  productions(limit: 10, sort: { title: ASC }) {
    title
    ... on Movie {
      released
    }
    actors {
      name
    }
  }
}

Response:

{
  "data": {
    "productions": [
      {
          "title": "Argo",
          "released": 2012,
          "actors": [{ "name": "Ben Affleck" }],
      },
      {
          "title": "Gone Girl",
          "released": 2014,
          "actors": [{ "name": "Ben Affleck" }],
      },
      {
          "title": "The Voyage of the Mimi",
          "actors": [{ "name": "Ben Affleck" }],
      },
      {
          "title": "Buffy the Vampire Slayer",
          "actors": [{ "name": "Ben Affleck" }],
      },
    ],
  }
}
Example 2. Querying Production nodes with related Actor nodes and relationship properties

Because of the different relationship properties types, ActedIn and ActedInSeries, the query must use inline fragments to access the relationship properties:

query {
  productionsConnection(first: 10, sort: [{ title: ASC }]) {
    edges {
      node {
        title
        actorsConnection(first: 2, sort: { node: { name: ASC } }) {
          edges {
            node {
              name
            }
            properties {
              ... on ActedIn {
                roles
              }
              ... on ActedInSeries {
                roles
                episodes
              }
            }
          }
        }
      }
    }
  }
}

Response:

{
  "data": {
    "productionsConnection": {
      "edges": [
        {
          "node": {
            "title": "Argo",
            "actorsConnection": {
              "edges": [{
                  "node": {
                      "name": "Ben Affleck",
                  },
                  "properties": {
                      "roles": ["Tony Mendez"],
                  },
                }],
            },
          },
        },
        {
          "node": {
            "title": "Buffy the Vampire Slayer",
            "actorsConnection": {
              "edges": [{
                "node": {
                    "name": "Ben Affleck",
                },
                "properties": {
                    "roles": ["Basketball Player #10"],
                    "episodes": 1,
                },
              }],
            },
          },
        },
        {
          "node": {
            "title": "Gone Girl",
            "actorsConnection": {
              "edges": [{
                "node": {
                    "name": "Ben Affleck",
                },
                "properties": {
                    "roles": ["Nick Dunne"],
                },
              }],
            },
          },
        },
        {
          "node": {
            "title": "The Voyage of the Mimi",
            "actorsConnection": {
              "edges": [{
                "node": {
                    "name": "Ben Affleck",
                },
                "properties": {
                    "roles": ["C.T. Granville"],
                    "episodes": 7,
                },
              }],
            },
          },
        },
      ],
    },
  },
}

Relationships to an interface

Person type nodes are connected to Movie and Series nodes but this relationship has not been modeled in the GraphQL schema yet.

You can do so by adding relationship fields to the Person type that point to the Production interface, and then implementing the relationship details in the Movie and Series types.

Extending the Person type with new relationship fields
type Person @node {
  name: String!
  born: Int!
  # relationship to an interface
  actedIn: [Production!]! @relationship(type: "ACTED_IN", properties: "ActedIn", direction: OUT)
}

Querying the relationships of an interface

When querying a relationship field of an interface type, the interface fields can be queried directly as they are conceptually shared by all the implementations of the interface, for example title for Production.

To query fields specific to the types implementing the interface, use inline fragments, for example released for Movie and episodes for Series.

Example 3. Get all Person nodes with all Production nodes they ACTED_IN

Notice the use of inline fragments to access the fields specific to the concrete types Movie and Series.

query {
    people {
        name
        actedIn {
            title
            ... on Movie {
                released
            }
            ... on Series {
                episodes
            }
        }
    }
}

Filtering for specific implementations of an interface

When querying a relationship field of an interface type, you can request specific node types by using the typename filter.

Example 4. Get all Person nodes with Production nodes they ACTED_IN filtered by the Movie type
query {
  people {
    name
    actedIn(where: { typename: [Movie] }) {
      title
      ... on Movie {
        released
      }
    }
  }
}

Notice the typename filter and the use of inline fragments to access the fields specific to the type Movie.

Creating a relationship to an interface

When creating a relationship to an interface type, you must specify the actual type of the node being connected in the mutation.

Example 5. Create a Person node with related Production nodes
mutation CreateActorAndProductions {
  createPeople(
    input: [
      {
        name: "Ben Affleck"
        actedIn: {
          create: [
            {
              edge: { roles: ["Tony Mendez"] }
              node: { Movie: { title: "Argo", released: 2012 } }
            }
            {
              edge: { roles: ["Nick Dunne"] }
              node: { Movie: { title: "Gone Girl", released: 2014 } }
            }
            {
              edge: { roles: ["Basketball Player #10"] }
              node: { Series: { title: "Buffy the Vampire Slayer", episodes: 144 } }
            }
            {
              edge: { roles: ["C.T. Granville"] }
              node: { Series: { title: "The Voyage of the Mimi", episodes: 7 } }
            }
          ]
        }
      }
    ]
  ) {
        people {
            name
        }
    }
}

Notice the specification of the type (Movie or Series) of the node that is being created.

Updating a relationship to an interface

Operations on interfaces apply for all implementing types unless you narrow them down.

Person Movie Series creation
Figure 3. Connecting new nodes

The mutation below adds the newly released Movie "Henry Danger" to the database, and connects it to the Person node Jace Norman through an ACTED_IN relationship with the role "Henry Hart". A Series node with the same title already exists.

To add the desired connections, use the typename filter in the filters of an update mutation.

Example 6. Updating Person nodes and creating ACTED_IN relationships to Production nodes filtered by Movie type

Notice the use of the typename filter to only target Movie nodes.

mutation {
  updatePeople(
    where: { name: { eq: "Jace Norman" } }
    update: {
      actedIn: [
        {
          connect: [
            {
              edge: { roles: ["Henry Hart"] }
              where: {
                node: {
                  typename: [Movie],
                  title: { eq: "Henry Danger" }
                },
              },
            },
          ],
        },
      ],
    }
  ) {
    info {
      relationshipsCreated
    }
  }
}

If the relationship between Person and Movie already exists in the database, make sure the relationship properties are correct depending on the type of the connected node.

Example 7. Updating Person nodes and ACTED_IN relationships to Production nodes with relationship properties based on type
mutation {
  updatePeople(
    where: {
      name: {
        eq: "Jace Norman"
      }
    }
    update: {
      actedIn: [
        {
          update: {
            where: { node: { title: { eq: "Henry Danger" } } }
            node: {
              actors: [
                {
                  update: {
                    edge: {
                      ActedIn: { roles: { set: ["Henry Hart"] } },
                      ActedInSeries: { roles: { set: ["Henry Hart"] }, episodes: { set: 121 } },
                    },
                  },
                },
              ],
            },
          },
        }]
    }
  ) {
    info {
      relationshipsCreated
    }
  }
}

Notice that the edge properties are updated with different values depending on the typename of the connected node.