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
Take the following graph as an example in which a Person type has two different relationship types, which can connect it to a both a Movie and a Series type.
The ACTED_IN relationship has different properties depending on the target node type.
Sample database data
Consider a database consisting of the following sample data, which follows the data model described above.
Relationships on an interface
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 will then be implemented by concrete object types, you can use the @declareRelationship directive.
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.
Movie and Series implementing the Production Interfaceinterface 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 concrete types that implement the interface, just like any other field on an interface type.
For instance, the |
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 types that implement the interface.
Person type# 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!
The focus is on the productions and productionsConnection query fields.
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
}
}
}
Assuming the sample data of this page, the result of the query is the following:
{
"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" }],
},
],
}
}
The query above returns a sorted list of up to 10 productions, that is a mix of Movie and Series nodes, with their related actors along with the relationship properties of the ACTED_IN relationship.
Because of the different relationship properties type, 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
}
}
}
}
}
}
}
}
Assuming the sample data of this page, the response of the query is the following:
{
"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
For the data model described at the beginning of this chapter, the 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 concrete types.
Person type with new relationship fieldstype 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 these are conceptually shared by all the implementations of the interface, for example title for Production.
To query fields specific to the concrete types implementing the interface, inline fragments must be used, for example released for Movie and episodes for Series.
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 the interface
When querying a relationship field of an interface type, receiving nodes of all implementing types is not mandatory.
If desired, you can request specific node types by using the typename filter.
Notice the typename filter and the use of inline fragments to access the fields specific to the concrete type Movie.
query {
people {
name
actedIn(where: { typename: [Movie] }) {
title
... on Movie {
released
}
}
}
}
Creating a relationship to an interface
When creating a relationship to an interface type, the concrete type of the node that is being connected must be specified in the mutation.
Notice the specification of the concrete type (Movie or Series) of the node that is being created.
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
}
}
}
Updating a relationship to an interface
Operations on interfaces apply for all implementing concrete types unless you narrow them down.
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".
Note that a Series node with the same title already exists.
To achieve the desired connections, use the typename filter in the filters of an update mutation.
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.
Notice the edge properties are updated with different values depending on the typename of the connected node.
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
}
}
}