After creating a basic set of nodes and establishing relationships between them I was able to execute queries that allowed one node to find linked nodes that weren’t directly related using a node that both sets of nodes were related to.
Once the landing page was live I decided to revisit the proof-of-concept as something was nagging at me — The graph database provided the means to query the product knowledge graph but I hadn’t yet planned out how I’d build an application on top of it.
The vision I have for Clime is to be a centralised repository for connecting product knowledge that is accessible from within the tools that the team uses (e.g. you could right-click a design in Figma and get a list of user stories it’s linked to) via series of plugins and integrations built by myself or a community of users.
While it would likely be easy enough to build a library to deal with querying Neo4J directly I felt that a REST or GraphQL API would be easier for others to work with, which is key if I’m looking to build a product that people can build their own programs for.
After a bit of research, I was really excited to see that Neo4J already has a solution for building a GraphQL wrapper around a graph database — the @neo4j/graphql package.
This package when combined with the Neo4J driver (a library for accessing a Neo4J instance) allows you to create a GraphQL schema for the nodes in your Neo4J graph and provides some handy GraphQL directives to make querying relationships easy to define. The GraphQL schema can then be used with Apollo or your GraphQL server of choice.
Creating a GraphQL schema for Neo4J
Before we can build an API we need to have a data model we’re looking to expose via that API. For an API over a graph database, this model needs to include both the nodes and edges as the relationships the edges represent will be key for returning linked data.
To keep things simple I’m going to re-use the model I used for the Clime proof-of-concept
Mockup — (A design, :VISUALISES a UserStory )
UserStory — (A definition of the system’s behaviour, :DEFINES a Product )
Product — (A Product, top-level object)
Test — (A test executed to ensure the system behaves correctly, :TESTS a UserStory )
Based on this model here’s a Cypher query that will add a sample set of data to Neo4J:
In order to create the GraphQL API for this model we then need to create type definitions so the Neo4jGraphQL constructor knows how to map the nodes in Neo4J to GraphQL objects. To start with we can just include the fields these objects will expose (we’ll cover linked nodes later).
Once the schema is set in Apollo you can then query those types and fields as you would normally do with GraphQL.
Returning linked nodes
The basic fields that are currently being returned are enough to see the values of the nodes but the main reason for using a graph database is to be able to find related nodes and the @neo4j/graphql library offers a few GraphQL directives to make this easy to set up.
The @relationship directive allows you to add fields for nodes that are directly linked to the source node being defined. It takes two arguments; type which is the type of edge that links the two nodes and direction which is the direction of the relationship ( IN for the other node defining the edge, OUT if the source node defines the edge).
Returning data from the edges that link nodes
A @relationship field can be expanded using the @relationshipProperties directive to include properties set during the definition of the edge between two nodes. The @relationship field definition is a properties argument which matches a type that implements the @relationshipProperties interface.
When querying the connected field you won’t be able to access the edge data from within the field, instead, you’ll need to query the [FIELD NAME]Connection (e.g. userStoriesConnection) object which contains the edge data and the node
Returning data from non-directly linked nodes
If there’s no clear relationship between the source node and another node that you want to return because they share a common, related node you can use the @cypher directive to execute a Cypher query and return the results as a field.
Here’s how you’d use these directives in your schema:
And an example query for fetching the relationships:
Writing data to Neo4J via GraphQL
The @neo4j/graphql library creates a useful set of mutations for performing Create-Read-Update-Delete (CRUD) actions on the types defined in the GraphQL schema we’ve already covered (read above), but here’s how you can create, update and delete nodes and edges.
Adding nodes and edges
To create a new node you can call the create[TYPE NAME] (e.g createTest) mutation which will take an input object which is the non-ID fields for the type. An example of this would be:
Updating nodes and edges
To update a node, you can call the update[TYPE NAME] (e.g. updateTest) mutation which will take a where object (for finding the node to update) and an update object with the fields to be updated on that node.
You can use the same update[TYPE NAME] mutation to create new edges between the nodes by defining a connect or connectOrCreate object with a where object to find the source node and a where object to find the target node (the difference is that connectOrCreate will create the node being linked to if it doesn’t exist).
Removing nodes and edges
If you want to remove an edge between two nodes you can use the same update[TYPE NAME] mutation but instead of a connect object you define a disconnect object with a where object to find the node to remove the link with.
Removing a node is done using the delete[TYPE NAME] (e.g. deleteTest) mutation with a where object to find the node to delete.
Using the API within an app
There are a number of GraphQL clients that you can use within your app to pull data from the Neo4J graph using GraphQL.
The proof-of-concept repo linked at the top of the page contains a basic Figma plugin I created to use the GraphQL API, it uses the Apollo client for React which exposes the Apollo client via a React Context and has useQuery and useMutation hooks to perform operations within lower-level components.
These hooks are performing the same queries and mutations as listed above so it’s really easy to build up these calls by using the Apollo Sandbox and copying them over.
The @neo4j/graphql library creates a really powerful API on top of Neo4J that makes it really easy for those with little graph database experience (like myself) to build apps that can leverage the benefits a graph database offers when working with relationships between records.
The CRUD examples and directives I’ve covered are really only at the surface of functionality the library has so I highly recommend reading the Neo4J GraphQL library documentation as it’s very well organised and insightful.
You can find my proof-of-concept code which contains the Neo4J test data, GraphQL server, and example Figma plugin I created to prove my idea on my Gitlab: