Subscription events
This page covers a variety of subscription options offered by the Neo4j GraphQL Library.
Only changes made through |
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 theUPDATE
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.
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 theCREATE_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 torelationshipFieldName
should benull
, the ones which are related should contain the relationship properties, if defined, and anode
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 theCREATE_RELATIONSHIP
operation took place.
Irrespective of the relationship direction in the database, the |
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 theMovie
andActor
types and, as such, can be considered a reciprocal relationship. -
DIRECTED
, which is only defined in theMovie
type. TheDirector
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
typesubscription {
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
typesubscription {
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 |
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 theDELETE_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 torelationshipFieldName
should benull
, 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 theDELETE_RELATIONSHIP
operation took place.
Irrespective of the relationship direction in the database, the |
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"
}
}
}
},