DEV Community

Cover image for Neo4j and GraphQL Heavenly Match #1 - Directional Relationships
Michael Porter
Michael Porter

Posted on • Updated on

Neo4j and GraphQL Heavenly Match #1 - Directional Relationships

Easily one of the most useful features of the GRANDstack's integration with GraphQL is the ability to create and express directional relationships between nodes in your graph, or entities in your data. A simple example would be two friends, which you could express in your GraphQL Schema like:

type Person {
  name: String
  friends: [Person] @relation(name: "FRIEND", direction: "OUT)
}

You can easily expand on this simple schema and add any amount of relationships that you desire like; favorite restaurants, hangouts, workplaces, family, etc. In addition if you want to place properties on the relationship you can do that as well by making the relationship itself a type in your schema, lets say you want to track a persons work history:

type Workplace {
  name: String
  address: String
  pastEmployees: [Person] @relation(name: "WORKED_AT", direction: "IN")
  currentEmployees: [Person] @relation(name: "WORKS_AT", direction: "IN")
}

type WorkedAt @relation(name: "WORKED_AT"){
   startDate: Date
   endDate: Date
   startSalary: Float
   endSalary: Float
}

You then expand your Person type:

type Person {
  name: String
  worksAt: WorkPlace @ relation(name: "WORKS_AT", direction: "OUT")
  workedAt: [WorkPlace] @relation(name: "WORKED_AT", direction: "OUT")
}

This gives you the ability to easily create a complete history of work for your Person and allows you to track current and past employees of your Workplace. The connections between the two then provide you the ability to dig deep into the history of both. What if you wanted to know the life time average ending salary for your person? Because you're using the GRANDstack you have access to Cypher directives via Neo4j:

type Person {
  name: String
  worksAt: WorkPlace @ relation(name: "WORKS_AT", direction: "OUT")
  workedAt: [WorkPlace] @relation(name: "WORKED_AT", direction: "OUT")
  avgEndingSalary: Float @cypher (
     statement: "MATCH (this)-[r:WORKED_AT]->(:WorkPlace) return avg(r.endSalary)
}

You can see how this ability to query relationships and the properties on them allows you to add a wealth of depth and detail to your application. You don't have to stop there, with the dates provided you can track avg time spent at each job or get the exact amount of days a person worked somewhere, the number of details you can now extract from this data only grows. The directional nature of the relationships set also allow you to control the flow of information and you can begin to use Graph Algorithms to make sense of your data. Want to find the shortest path between two companies via past or present employees? There's a Graph Algorithm for that! Want to find a person who can act as a bridge between two companies? There's a Graph Algorithm for that too! Being able to create and track these numerous relationships and their properties is just one of the reasons that GraphQL and Neo4j with the GRANDstack are a match made in heaven.

  • note: This is a simple use case and would not be optimized for very large data set queries. A heavenly Match

Top comments (4)

Collapse
 
abiyasa profile image
Abiyasa Suhardi • Edited

Thanks for the article 👍 What I really like about GRANDStack is I can express the data model and the Cypher query in my GraphQL Schema.

I have 2 questions from your article:

  • You declared the WorkedAt type on your schema but I haven't seen it being used. Let say you want to expand the Person type to also return the list of company they've worked in the past, including the start & end date. How would the schema would look like?
  • On the footnote, you mentioned that this is a simple use case and would not be optimized for very large data set queries. Could you elaborate more about this? for example, your schema is not optimized for that, or there is a scaling problem on GRAND stack itself, or how to make it more optimized for a large data set

Sorry if I ask too much. Thanks a lot 🙂

Collapse
 
muddybootscode profile image
Michael Porter

No worries Abiyasa let me see if I can answer those questions:
1) In the schema, you can already get a list of the places that a person has WorkedAt the field returns an array of workplaces like so:

workedAt: [WorkPlace] @relation(name: "WORKED_AT", direction: "OUT")

As for the start and end dates as well as the company worked at you're going to have to make some compromises because you can only return scalar types from the cypher queries so:

workHistory: [string] @cypher (
   statement: "MATCH (this)-[r:WORKED_AT]->(w:WorkPlace) return r.startDate, r.endDate, w.name"
)

and then create a json object or something of that nature to take care of it.

As for a more performant version you would need to include in between the Person and workplace nodes, an intermediary node that captures the workplace information i.e. startDate, endDate, etc. because you can't index properties on a relationship and that slows the query down on massive data sets.

Collapse
 
abiyasa profile image
Abiyasa Suhardi

Thanks, Michael! That's really helpful, especially about returning scalar types, which I didn't know before about this limitation

Thread Thread
 
muddybootscode profile image
Michael Porter • Edited

No problem, in this case you're probably going to want to return some kind of custom scalar type, like a JSON object. You can find information about there here in the Apollo docs apollographql.com/docs/graphql-too...