Subscription events

This page covers a variety of subscription options offered by the Neo4j GraphQL Library.

Only changes made through @neo4j/graphql should trigger the events here described. Changes made directly to the database or using the @cypher directive will not trigger any event.

CREATE

Subscriptions to CREATE events listen only to newly created nodes, not new relationships. In this occasion, a new event is triggered for each new node, containing its properties.

This action is performed with the top-level subscription [type]Created, which contains the following fields:

  • event: the event triggering this subscription (in this case, CREATE).

  • created<typename>: top-level properties of the newly created node, without relationships.

  • timestamp: the timestamp in which the mutation was made. If a same query triggers multiple events, they should have the same timestamp.

As an example, consider the following type definitions:

type Movie {
    title: String
    genre: String
}

A subscription to any newly created node of the Movie type should look like this:

subscription {
    movieCreated {
        createdMovie {
            title
            genre
        }
        event
        timestamp
    }
}

UPDATE

Subscriptions to UPDATE events listen only to node properties changes, not updates to other fields. In this occasion, a new event is triggered for each mutation that modifies the node top-level properties.

This action is performed with the top-level subscription [type]Updated, which contains the following fields:

  • event: the event triggering this subscription (in this case, UPDATE).

  • updated<typename>: top-level properties of the updated node, without relationships.

  • previousState: the previous top-level properties of the node, before the UPDATE event.

  • timestamp: the timestamp in which the mutation was made. If a same query triggers multiple events, they should have the same timestamp.

As an example, consider the following type definitions:

type Movie {
    title: String
    genre: String
}

A subscription to any node of the Movie type with its properties recently updated should look like this:

subscription MovieUpdated {
    movieUpdated {
        event
        previousState {
            title
            genre
        }
        updatedMovie {
            title
        }
        timestamp
    }
}

DELETE

Subscriptions to DELETE events listen only to nodes being deleted, not deleted relationships. This action is performed with the top-level subscription [type]Deleted, which contains the following fields:

  • event: the event triggering this subscription (in this case, DELETE).

  • deleted<typename>: top-level properties of the deleted node, without relationships.

  • timestamp: the timestamp in which the mutation was made. If a same query triggers multiple events, they should have the same timestamp.

As an example, consider the following type definitions:

type Movie {
    title: String
    genre: String
}

A subscription to any deleted nodes of the Movie type should look like this:

subscription {
    movieDeleted {
        deletedMovie {
            title
        }
        event
        timestamp
    }
}

CREATE_RELATIONSHIP

Subscriptions to CREATE_RELATIONSHIP events listen to new relationships being created and contain information about the connected nodes. These events:

  • Are only available for types that define relationship fields.

  • Contain relationship-specific information, such as the relationship field name and the object containing all relationship field names of the specified type.

  • Trigger an equivalent number of events compared to the relationships created, in case a new relationship is created following a mutation and the type targeted is responsible for defining two or more relationships in the schema.

  • Contain the relationships object populated with the newly created relationship properties for one single relationship name only (all other relationship names should have a null value).

  • Contain the properties of the nodes connected through the relationship, as well as the properties of the new relationship, if any.

Connected nodes that may or may not have previously existed are not covered by this subscription. To subscribe to these nodes' updates, use the CREATE or the UPDATE subscription.

Subscriptions to CREATE_RELATIONSHIP events can be made with the top-level subscription [type]RelationshipCreated, which contains the following fields:

  • event: the event triggering this subscription (in this case, CREATE_RELATIONSHIP).

  • timestamp: the timestamp in which the mutation was made. If a same query triggers multiple events, they should have the same timestamp.

  • <typename>: top-level properties of the targeted nodes, without relationships, before the CREATE_RELATIONSHIP operation was triggered.

  • relationshipFieldName: the field name of the newly created relationship.

  • createdRelationship: an object having all field names of the nodes affected by the newly created relationships. While any event unrelated to relationshipFieldName should be null, the ones which are related should 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 and they are the properties that already existed before the CREATE_RELATIONSHIP operation took place.

Irrespective of the relationship direction in the database, the CREATE_RELATIONSHIP event is bound to the type targeted for the subscription. Consequently, if types A and B have non-reciprocal relationships and a GraphQL operation creates a relationship between them (despite being already previously connected in the database), the CREATE_RELATIONSHIP event should only return the subscription to the type A.

As an example, consider 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
}

type ActedIn @relationshipProperties {
    screenTime: Int!
}

type Reviewer {
    name: String
    reputation: Int
}

type Reviewed @relationshipProperties {
    score: Int!
}

Now consider a mutation creating an Actor named Tom Hardy and a Reviewer named Jane is connected through a relationship to a Movie titled Inception. A CREATE_RELATIONSHIP subscription in this case should 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
            }
        }
    }
}

Standard types

For another example, this time creating a relationship with standard types, consider the following type definitions:

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

type Actor {
    name: String
}

type ActedIn @relationshipProperties {
    screenTime: Int!
}

A subscription to any Movie with newly created relationships should look like this:

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

Abstract types

When using abstract types with relationships, you 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.

As an example, consider 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
}

type Directed @relationshipProperties {
    year: Int!
}

A subscription to any Movie newly created relationships should look like this:

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

Interface

For an example in which a relationship is created with an interface, consider 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!
}

type Review @relationshipProperties {
    score: Int!
}

A subscription to any Movie newly created relationships should look like this:

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

Non-reciprocal relationships can be described, for example, as when a type A and a type B hold a relationship, but, in the GraphQL schema, type A is the one defining the relationship to B, while B does not define a relationship to A.

To illustrate that, consider 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

type ActedIn @relationshipProperties {
    screenTime: Int!
}

type Directed @relationshipProperties {
    year: Int!
}

Note that the type definitions contain two relationships:

  • ACTED_IN, which has a corresponding field defined in both the Movie and Actor types and, as such, can be considered a reciprocal relationship.

  • DIRECTED, which is only defined in the Movie type. The Director type does not define a matching field and, as such, it can be considered a non-reciprocal relationship.

Considering the three types previously described (Movie, Actor, and Person), subscribing to CREATE_RELATIONSHIP is not possible only in the case of the Person type, for it does not define any relationships. For the other two types, here is how to subscribe:

Movie type
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
                    }
                }
            }
        }
    }
}
Actor type
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 created, such as by running this mutation:

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

Should prompt two events, in case you have 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"
            }
        }
    }
}

Now, since the DIRECTED relationship between types Movie and Director is not reciprocal, executing this mutation:

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
        }
    }
}

Should prompt two events, in case you have 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
            }
        }
    }
}

Types using the same Neo4j label

One scenario to be considered is when Neo4j labels are overriden by a specific GraphQL type. This can be achieved using the @node directive, by specifying the label argument. However, in the majority of cases, this is not the recommended approach to design your API.

As an example, consider these 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 the example features 3 GraphQL types, in Neo4j there should 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 should 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 such as:

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
        }
    }
}

Should result in this:

{
    # 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"
            }
        }
    }
},

In case you have subscribed to Person as well, you should receive 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"
            }
        }
    }
},

DELETE_RELATIONSHIP

Subscriptions to DELETE_RELATIONSHIP events listen to relationships being deleted and contain information about the previously connected nodes of a specified type. This kind of subscription:

  • Is only available for types that define relationship fields.

  • Contains relationship-specific information, such as the relationship field name and the object containing all relationship field names of the specified type. This object should be populated with properties according to the deleted relationship.

  • Triggers an equivalent number of events compared to relationships deleted, in case a relationship is deleted following a mutation and the type targeted is responsible for defining two or more relationships in the schema.

  • Contains the relationships object populated with the newly deleted relationship properties for one single relationship name only (all other relationship names should have a null value).

  • Contains the properties of the nodes connected through the relationship, as well as the properties of the newly deleted relationship, if any.

Disconnected nodes that may or may not have been deleted in the process are not covered by this subscription. To subscribe to these nodes' updates, use the DELETE subscriptions.

Subscriptions to DELETE_RELATIONSHIP events can be made with the top-level subscription [type]RelationshipDeleted, which contains the following fields:

  • event: the event triggering this subscription (in this case, DELETE_RELATIONSHIP).

  • timestamp: the timestamp in which the mutation was made. If a same query triggers multiple events, they should have the same timestamp.

  • <typename>: top-level properties of the targeted nodes, without relationships, before the DELETE_RELATIONSHIP operation was triggered.

  • relationshipFieldName: the field name of the newly deleted relationship.

  • deletedRelationship: an object having all field names of the nodes affected by the newly deleted relationships. While any event unrelated to relationshipFieldName should be null, the ones which are related should 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 and they are the properties that already existed before the DELETE_RELATIONSHIP operation took place.

Irrespective of the relationship direction in the database, the DELETE_RELATIONSHIP event is bound to the type targeted for the subscription. Consequently, if types A and B have non-reciprocal relationships and a GraphQL operation deletes a relationship between them (despite being already previously disconnected in the database), the DELETE_RELATIONSHIP event should only return the subscription to the type A.

As an example, consider these 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 {s
    name: String
}

type ActedIn @relationshipProperties {
    screenTime: Int!
}

type Reviewer {
    name: String
    reputation: Int
}

type Reviewed @relationshipProperties {
    score: Int!
}

Now consider a mutation deleting the Actor named Tom Hardy and the Reviewer named Jane, which are connected through a relationship to a Movie titled Inception. A DELETE_RELATIONSHIP subscription in this case should receive the following events:

{
    # 1  - relationship type `ACTED_IN`
    event: "DELETE_RELATIONSHIP",
    timestamp,
    movie: {
        title: "Inception",
        genre: "Adventure"
    },
    relationshipFieldName: "actors", # notice the field name specified here is populated below in the `createdRelationship` object
    deletedRelationship: {
        actors: {
            screenTime: 1000, # relationship properties for the relationship type `ACTED_IN` that was deleted
            node: { # top-level properties of the node at the other end of the relationship, in this case `Actor` type, before the delete occurred
                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: "DELETE_RELATIONSHIP",
    timestamp,
    movie: {
        title: "Inception",
        genre: "Adventure"
    },
    relationshipFieldName: "reviewers", # this event covers the relationship declared by this field name
    deletedRelationship: {
        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
            }
        }
    }
}

Standard types

As an example, consider these type definitions:

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

type Actor {
    name: String
}

type ActedIn @relationshipProperties {
    screenTime: Int!
}

A subscription to any Movie deleted relationships would look like:

subscription {
    movieRelationshipDeleted {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        deletedRelationship {
            actors {
                screenTime
                node {
                    name
                }
            }
        }
    }
}

Delete 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
}

type Directed @relationshipProperties {
    year: Int!
}

A subscription to Movie deleted relationships would look like:

subscription {
    movieRelationshipDeleted {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        deletedRelationship {
           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!
}

type Review @relationshipProperties {
    score: Int!
}

A subscription to Movie deleted relationships would look like:

subscription {
    movieRelationshipDeleted {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        deletedRelationship {
            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

type ActedIn @relationshipProperties {
    screenTime: Int!
}

type 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 deleted relationships for the 3 types defined above:

Movie
subscription {
    movieRelationshipDeleted {
        event
        timestamp
        movie {
            title
            genre
        }
        relationshipFieldName
        deletedRelationship {
           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 DELETE_RELATIONSHIP events for this type.

Actor
subscription {
    actorRelationshipDeleted {
        event
        timestamp
        actor {
            name
        }
        relationshipFieldName
        deletedRelationship {
           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 deletedRelationship for the actorRelationshipDeleted subscription reflects the fact that the ACTED_IN typed relationship is reciprocal.

Therefore, when a relationship of this type is deleted, such as by running the following mutations:

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

mutation {
    deleteMovies(
        where: {
            title: "John Wick"
        }
    ) {
        nodesDeleted
    }
}

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

{
    # from `movieRelationshipDeleted`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "John Wick",
        genre: "Action"
    }
    relationshipFieldName: "actors",
    deletedRelationship {
        actors: {
            screenTime: 420,
            node: {
                name: "Keanu Reeves"
            }
        },
        directors: null
    }
},
{
    # from `actorRelationshipDeleted`
    event: "DELETE_RELATIONSHIP"
    timestamp
    actor {
        name: "Keanu Reeves"
    }
    relationshipFieldName: "movies",
    deletedRelationship {
        movies: {
            screenTime: 420,
            node: {
                title: "John Wick",
                genre: "Action"
            }
        }
    }
}

Since the DIRECTED relationship between types Movie and Director is not reciprocal, executing the following mutations:

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
        }
    }
}

mutation {
    deleteMovies(
        where: {
            title: "New York Stories"
        }
    ) {
        nodesDeleted
    }
}

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

{
    # relationship 1 - from `movieRelationshipDeleted`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "New York Stories",
        genre: "Comedy"
    }
    relationshipFieldName: "directors",
    deletedRelationship {
        actors: null,
        directors: {
            year: 1989,
            node: {
                name: "Woody Allen"
            }
        }
    }
},
{
    # relationship 2 - from `movieRelationshipDeleted`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "New York Stories",
        genre: "Comedy"
    }
    relationshipFieldName: "directors",
    deletedRelationship {
        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 deleting a relationship of type PART_OF, there will be one event for each of the 2 types.

Considering the following subscriptions:

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

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

Running the following mutations:

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
        }
    }
}

mutation {
    deleteMovies(
        where: {
            title: "Sweeney Todd"
        }
    ) {
        nodesDeleted
    }
}

Result in the following events:

{
    # relationship 1 `people` - for GraphQL types `Movie`, `Person`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "people",
    deletedRelationship {
        people: {
            node: {
                name: "John Logan"
            }
        },
        actors: null
    }
},
{
    # relationship 1 `people` - for GraphQL types `Movie`, `Actor`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "actors",
    deletedRelationship {
        people: null,
        actors: {
            node: {
                name: "John Logan"
            }
        }
    }
},
{
    # relationship 1 `movies` - for GraphQL types `Actor`, `Movie`
    event: "DELETE_RELATIONSHIP"
    timestamp
    actor {
        name: "John Logan"
    }
    relationshipFieldName: "movies",
    deletedRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},
{
    # relationship 2 `actors` - for GraphQL types `Movie`,`Person`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "people",
    deletedRelationship {
        people: {
            node: {
                name: "Johnny Depp"
            }
        },
        actors: null
    }
},
{
    # relationship 2 `actors` - for GraphQL types `Movie`, `Actor`
    event: "DELETE_RELATIONSHIP"
    timestamp
    movie {
        title: "Sweeney Todd",
        genre: "Horror"
    }
    relationshipFieldName: "actors",
    deletedRelationship {
        people: null,
        actors: {
            node: {
                name: "Johnny Depp"
            }
        }
    }
},
{
    # relationship 2 `movies` - for GraphQL types `Actor`, `Movie`
    event: "DELETE_RELATIONSHIP"
    timestamp
    actor {
        name: "Johnny Depp"
    }
    relationshipFieldName: "movies",
    deletedRelationship {
        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: "DELETE_RELATIONSHIP"
    timestamp
    actor {
        name: "John Logan"
    }
    relationshipFieldName: "movies",
    deletedRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},
{
    # relationship 2 `movies` - for GraphQL types `Person`, `Movie`
    event: "DELETE_RELATIONSHIP"
    timestamp
    actor {
        name: "Johnny Depp"
    }
    relationshipFieldName: "movies",
    deletedRelationship {
        movies: {
            node: {
                title: "Sweeney Todd",
                genre: "Horror"
            }
        }
    }
},