Create Relationship Subscriptions

Subscriptions to CREATE_RELATIONSHIP events will listen for newly created relationships to a node of the specified type.

This subscription operation is only available for types that define relationship fields.

As relationship-specific information, the event will contain the relationship field name, as well as an object containing all relationship field names of the specified type. This object will be populated with properties according to the newly created relationship.

A new event will be triggered for each new relationship.

This means that, if the type targeted for the subscriptions defines two or more relationships in the schema and one of each relationships are created following a mutation operation, the number of events triggered will be equivalent to the number of relationships created.

Each event will have the relationships object populated with the created relationship’s properties for one single relationship name only - all other relationship names will have a null value.

The event will also contain the properties of the nodes at both ends of the relationship, as well as the properties of the new relationship, if any.

The CREATE_RELATIONSHIP events represent new relationships being created and contain information about the nodes at each end of the new relationship. However, the connected nodes may or may not have previously existed. To subscribe to the node’s updates, you need to use one of the CREATE or UPDATE subscriptions.

CREATE_RELATIONSHIP event

A subscription to a type can be made with the top-level subscription [type]RelationshipCreated. The subscription will contain the following fields:

  • event: The event triggering this subscription, in this case it will always be "CREATE_RELATIONSHIP".

  • timestamp: The timestamp in which the mutation was made. Note that multiple events may come with the same timestamp if triggered by the same query.

  • <typename>: The properties of the node target to the subscription. Only top-level properties, without relationships, are available. Note these are the properties before the operation that triggered the CREATE_RELATIONSHIP took place.

  • relationshipFieldName: The field name of the newly created relationship, as part of the node target to the subscription.

  • createdRelationship: An object having as keys all field names of the node target to the subscription which represent its relationships. For any given event, all field names except the one corresponding to relationshipFieldName will be null. The field name equal to relationshipFieldName will contain the relationship properties if defined, and a node key containing the properties of the node on the other side of the relationship. Only top-level properties, without relationships, are available. Note these are the properties before the operation that triggered the CREATE_RELATIONSHIP took place.

Irrespective of the relationship direction in the database, the CREATE_RELATIONSHIP event is bound to the type targeted for the subscription. The consequence is that - given a relationship between types A and B that is not reciprocal (that is, in the GraphQL schema type A defines a relationship to B but B does not define a relationship to A) and a GraphQL operation that creates the relationship between them - even though the two nodes will be connected in the database, the CREATE_RELATIONSHIP event will only be returned to the subscription to type A. Check out the Non-reciprocal Relationships section below for more details.

For example, considering the following type definitions:

type Movie {
    title: String
    genre: String
    actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
    reviewers: [Reviewer] @relationship(type: "REVIEWED", direction: IN, properties: "Reviewed")
}

type Actor {
    name: String
}

interface ActedIn @relationshipProperties {
    screenTime: Int!
}

type Reviewer {
    name: String
    reputation: Int
}

interface Reviewed @relationshipProperties {
    score: Int!
}

An ongoing subscription to created relationships on the Movie type, upon a mutation creating an Actor named Tom Hardy and a Reviewer named Jane to a Movie titled Inception would receive the following events:

{
    # 1  - relationship type `ACTED_IN`
    event: "CREATE_RELATIONSHIP",
    timestamp,
    movie: {
        title: "Inception",
        genre: "Adventure"
    },
    relationshipFieldName: "actors", # notice the field name specified here is populated below in the `createdRelationship` object
    createdRelationship: {
        actors: {
            screenTime: 1000, # relationship properties for the relationship type `ACTED_IN`
            node: { # top-level properties of the node at the other end of the relationship, in this case `Actor` type
                name: "Tom Hardy"
            }
        },
        reviewers: null # relationship declared by this field name is not covered by this event, check out the following...
    }
}
{
    # 2 - relationship type `REVIEWED`
    event: "CREATE_RELATIONSHIP",
    timestamp,
    movie: {
        title: "Inception",
        genre: "Adventure"
    },
    relationshipFieldName: "reviewers", # this event covers the relationship declared by this field name
    createdRelationship: {
        actors: null, # relationship declared by this field name is not covered by this event
        reviewers: { # field name equal to `relationshipFieldName`
            score: 8,
            node: {
                name: "Jane",
                reputation: 9
            }
        }
    }
}

Examples

Create Relationship with Standard Types

For example, considering the following type definitions:

type Movie {
    title: String
    genre: String
    actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
}

type Actor {
    name: String
}

interface ActedIn @relationshipProperties {
    screenTime: Int!
}

A subscription to any Movie created relationships would look like:

subscription {
    movieRelationshipCreated {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        createdRelationship {
            actors {
                screenTime
                node {
                    name
                }
            }
        }
    }
}

Create Relationship with Abstract Types

When using Abstract Types with relationships, you will need to specify one or more of the corresponding Concrete Types when performing the subscription operation.

These types are generated by the library and conform to the format [type]EventPayload, where [type] is a Concrete Type.

Union Example

Considering the following type definitions:

type Movie {
    title: String
    genre: String
    directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN)
}

union Director = Person | Actor

type Actor {
    name: String
}

type Person {
    name: String
    reputation: Int
}

interface Directed @relationshipProperties {
    year: Int!
}

A subscription to any Movie created relationships would look like:

subscription {
    movieRelationshipCreated {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        createdRelationship {
           directors {
                year
                node {
                    ... on PersonEventPayload { # generated type
                        name
                        reputation
                    }
                    ... on ActorEventPayload { # generated type
                        name
                    }
                }
            }
        }
    }
}

Interface Example

Considering the following type definitions:

type Movie {
    title: String
    genre: String
    reviewers: [Reviewer!]! @relationship(type: "REVIEWED", properties: "Review", direction: IN)
}

interface Reviewer {
    reputation: Int!
}

type Magazine implements Reviewer {
    title: String
    reputation: Int!
}

type Influencer implements Reviewer {
    name: String
    reputation: Int!
}

interface Review {
    score: Int!
}

A subscription to any Movie created relationships would look like:

subscription {
    movieRelationshipCreated {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        createdRelationship {
            reviewers {
                score
                node {
                    reputation
                    ... on MagazineEventPayload { # generated type
                        title
                        reputation
                    }
                    ... on InfluencerEventPayload { # generated type
                        name
                        reputation
                    }
                }
            }
        }
    }
}

Non-reciprocal relationships

Considering the following type definitions:

type Movie {
    title: String
    genre: String
    actors: [Actor] @relationship(type: "ACTED_IN", direction: IN, properties: "ActedIn")
    directors: [Director!]! @relationship(type: "DIRECTED", properties: "Directed", direction: IN)
}

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

type Person {
    name: String
    reputation: Int
}

union Director = Person | Actor

interface ActedIn @relationshipProperties {
    screenTime: Int!
}

interface Directed @relationshipProperties {
    year: Int!
}

The type definitions contain 2 relationships: types ACTED_IN and DIRECTED.

It can be observed that the ACTED_IN relationship has a corresponding field defined in both the Movie and Actor types. As such, we can say that ACTED_IN is a reciprocal relationship.

DIRECTED on the other hand is only defined in the Movie type. The Director type does not define a matching field. As such, we can say DIRECTED is not a reciprocal relationship.

Let us now take a look at how we can subscribe to created relationships for the 3 types defined above:

Movie

subscription {
    movieRelationshipCreated {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        createdRelationship {
           actors { # corresponds to the `ACTED_IN` relationship type
                screenTime
                node {
                    name
                }
           }
           directors { # corresponds to the `DIRECTED` relationship type
                year
                node {
                    ... on PersonEventPayload {
                        name
                        reputation
                    }
                    ... on ActorEventPayload {
                        name
                    }
                }
            }
        }
    }
}

Person

As the Person type does not define any relationships, it is not possible to subscribe to CREATE_RELATIONSHIP events for this type.

Actor

subscription {
    actorRelationshipCreated {
        event
        timestamp
        actor {
            name
        }
        relationshipFieldName
        createdRelationship {
           movies { # corresponds to the `ACTED_IN` relationship type
                screenTime
                node {
                    title
                    genre
                }
           }
           # no other field corresponding to the `DIRECTED` relationship type
        }
    }
}

The presence of the movie field inside of createdRelationship for the actorRelationshipCreated subscription reflects the fact that the ACTED_IN typed relationship is reciprocal.

Therefore, when a new relationship of this type is made, such as by running a mutation as follows:

mutation {
    createMovies(
        input: [
            {
                actors: {
                    create: [
                        {
                            node: {
                                name: "Keanu Reeves"
                            },
                            edge: {
                                screenTime: 420
                            }
                        }
                    ]
                },
                title: "John Wick",
                genre: "Action"
            }
        ]
    ) {
        movies {
            title
            genre
        }
    }
}

Two events will be published (given that we subscribed to CREATE_RELATIONSHIP events on both types):

{
    # from `movieRelationshipCreated`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "John Wick",
        genre: "Action"
    }
    relationshipFieldName: "actors",
    createdRelationship {
        actors: {
            screenTime: 420,
            node: {
                name: "Keanu Reeves"
            }
        },
        directors: null
    }
},
{
    # from `actorRelationshipCreated`
    event: "CREATE_RELATIONSHIP"
    timestamp
    actor {
        name: "Keanu Reeves"
    }
    relationshipFieldName: "movies",
    createdRelationship {
        movies: {
            screenTime: 420,
            node: {
                title: "John Wick",
                genre: "Action"
            }
        }
    }
}

Since the DIRECTED relationship between types Movie and Director is not reciprocal, executing a mutation as follows:

mutation {
    createMovies(
        input: [
            {
                directors: {
                    Actor: { # relationship 1
                        create: [
                            {
                                node: {
                                    name: "Woody Allen"
                                },
                                edge: {
                                    year: 1989
                                }
                            }
                        ]
                    },
                    Person: { # relationship 2
                        create: [
                            {
                                node: {
                                    name: "Francis Ford Coppola",
                                    reputation: 100
                                },
                                edge: {
                                    year: 1989
                                }
                            }
                        ]
                    }
                },
                title: "New York Stories",
                genre: "Comedy"
            }
        ]
    ) {
        movies {
            title
            genre
        }
    }
}

Two events will be published (given that we subscribed to CREATE_RELATIONSHIP events on the Movie type):

{
    # relationship 1 - from `movieRelationshipCreated`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "New York Stories",
        genre: "Comedy"
    }
    relationshipFieldName: "directors",
    createdRelationship {
        actors: null,
        directors: {
            year: 1989,
            node: {
                name: "Woody Allen"
            }
        }
    }
},
{
    # relationship 2 - from `movieRelationshipCreated`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "New York Stories",
        genre: "Comedy"
    }
    relationshipFieldName: "directors",
    createdRelationship {
        actors: null,
        directors: {
            year: 1989,
            node: {
                 name: "Francis Ford Coppola",
                reputation: 100
            }
        }
    }
}

Special Considerations

Types using the same Neo4j label

One case that deserves special consideration is overriding the label in Neo4j for a specific GraphQL type. This can be achieved using the @node directive, by specifying the label argument.

While this section serves an informative purpose, it should be mentioned that, in the majority of cases, this is not the recommended approach of designing your API.

Consider the following type definitions:

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

typePerson {
    name: String
    movies: [Movie!]! @relationship(type: "PART_OF", direction: OUT)
}

type Movie {
    title: String
    genre: String
    people: [Person!]!  @relationship(type: "PART_OF", direction: IN)
    actors: [Actor!]!  @relationship(type: "PART_OF", direction: IN)
}

Although we have 3 GraphQL types, in Neo4j there will only ever be 2 types of nodes: labeled Movie or labeled Person.

At the database level there is no distinction between Actor and Person. Therefore, when creating a new relationship of type PART_OF, there will be one event for each of the 2 types.

Considering the following subscriptions:

subscription {
    movieRelationshipCreated {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        createdRelationship {
           people { # corresponds to the `PART_OF` relationship type
                node {
                    name
                }
           }
           actors { # corresponds to the `PART_OF` relationship type
                node {
                    name
                }
           }
        }
    }
}

subscription {
    actorRelationshipCreated {
        event
        timestamp
        actor {
            name
        }
        relationshipFieldName
        createdRelationship {
           movies { # corresponds to the `PART_OF` relationship type
                node {
                    title
                    genre
                }
           }
        }
    }
}

Running a mutation as follows:

mutation {
    createMovies(
        input: [
            {
                people: { # relationship 1
                    create: [
                        {
                            node: {
                                name: "John Logan"
                            }
                        }
                    ]
                },
                actors: {  # relationship 2
                    create: [
                        {
                            node: {
                                name: "Johnny Depp"
                            }
                        }
                    ]
                },
                title: "Sweeney Todd",
                genre: "Horror"
            }
        ]
    ) {
        movies {
            title
            genre
        }
    }
}

Results in the following events:

{
    # relationship 1 `people` - for GraphQL types `Movie`, `Person`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "people",
    createdRelationship {
        people: {
            node: {
                name: "John Logan"
            }
        },
        actors: null
    }
},
{
    # relationship 1 `people` - for GraphQL types `Movie`, `Actor`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "actors",
    createdRelationship {
        people: null,
        actors: {
            node: {
                name: "John Logan"
            }
        }
    }
},
{
    # relationship 1 `movies` - for GraphQL types `Actor`, `Movie`
    event: "CREATE_RELATIONSHIP"
    timestamp
    actor {
        name: "John Logan"
    }
    relationshipFieldName: "movies",
    createdRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},
{
    # relationship 2 `actors` - for GraphQL types `Movie`,`Person`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "people",
    createdRelationship {
        people: {
            node: {
                name: "Johnny Depp"
            }
        },
        actors: null
    }
},
{
    # relationship 2 `actors` - for GraphQL types `Movie`, `Actor`
    event: "CREATE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "actors",
    createdRelationship {
        people: null,
        actors: {
            node: {
                name: "Johnny Depp"
            }
        }
    }
},
{
    # relationship 2 `movies` - for GraphQL types `Actor`, `Movie`
    event: "CREATE_RELATIONSHIP"
    timestamp
    actor {
        name: "Johnny Depp"
    }
    relationshipFieldName: "movies",
    createdRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},

Had we subscribed to Person as well, we would have received two more events:

{
    # relationship 1 `movies` - for GraphQL types `Person`, `Movie`
    event: "CREATE_RELATIONSHIP"
    timestamp
    actor {
        name: "John Logan"
    }
    relationshipFieldName: "movies",
    createdRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},
{
    # relationship 2 `movies` - for GraphQL types `Person`, `Movie`
    event: "CREATE_RELATIONSHIP"
    timestamp
    actor {
        name: "Johnny Depp"
    }
    relationshipFieldName: "movies",
    createdRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},