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 theCREATE_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 torelationshipFieldName
will be null. The field name equal torelationshipFieldName
will 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. Note these are the properties before the operation that triggered theCREATE_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"
}
}
}
},
Was this page helpful?