Building GraphQL APIs With The Neo4j GraphQL Library
& Neo4j Aura
About Me
What is Neo4j?
Graph Database
The Neo4j Graph Data Platform
4
Analytics &� Data Science �Tooling
Graph �Transactions
Data Orchestration
Development & �Administration
Drivers & APIs
Discovery & Visualization
Graph �Analytics
AI
BUSINESS USERS
DEVELOPERS
ADMINS
DATA�ANALYSTS
DATA�SCIENTISTS
APPLICATIONS
Cloud
Neo4j, Inc. All rights reserved 2021
Building GraphQL APIs With The Neo4j GraphQL Library
Agenda
Modules:
Hands-On exercises using:
Hands-On
Exercise
Resources
Introduction To GraphQL
7
What Is GraphQL?
GraphQL is an API query language and runtime for fulfilling those queries.
GraphQL uses a type system to define the data available in the API, including what entities and attributes (types and fields in GraphQL parlance) exist and how types are connected (the data graph).
GraphQL operations (queries, mutations, or subscriptions) specify an entry-point and a traversal of the data graph (the selection set) which defines what fields to be returned by the operation.
GraphQL Concepts - Type Definitions
GraphQL type definitions define the data available in the API.
These type definitions are typically defined using the GraphQL Schema Definition Language (SDL), a language-agnostic way of expressing the types.
However, type definitions can be also be defined programmatically.
GraphQL Concepts - GraphQL Operations
Each GraphQL operation is either a Query, Mutation, or Subscription.
GraphQL Concepts - GraphQL Operations
Each GraphQL operation is either a Query, Mutation, or Subscription.
The fields of the Query, Mutation, and Subscription types define the entry points for an operation.
Each operation starts at the field of one of these types.
Entry point & arguments
GraphQL Concepts - Selection Set
The selection set specifies the fields to be returned by a GraphQL operation.
Can be thought of as a traversal through the data graph.
Selection set
GraphQL Concepts - Selection Set
The response to a GraphQL operation matches the shape of the selection set, returning on the data requested.
Selection set
GraphQL Concepts - Resolver Functions
GraphQL resolvers are the functions responsible for actually fulfilling the GraphQL operation.
In the context of a query, this means fetching data from a data layer.
NOTE: The Neo4j GraphQL Library auto-generates resolver functions for us, but this is an important GraphQL concept to understand
Benefits Of GraphQL
GraphQL Challenges
Best practices and tooling have emerged to address all of the above, however it’s important to be aware of these challenges.
GraphQL Tooling - GraphQL Playground
GraphQL Playground is an in-browser tool for querying and exploring GraphQL APIs.
View API documentation using GraphQL's introspection feature.
GraphQL Tooling - GraphQL Playground
{
movies(options: { limit: 10 }) {
title
actors {
name
}
}
}
{
directors(where: {name:"Robert Redford"}) {
name
directed {
title
plot
}
}
}
Hands On
Exercise
Let's Build Our Own GraphQL API!
19
Neo4j Aura Free Tier Setup
Let's create a Neo4j Aura Free instance that we'll use for the rest of the workshop...
Hands-On
Exercise
Once your Neo4j Aura instance is online you'll see the connection string (neo4j+s://xxxxx.databases.neo4j.io)
Be sure to take note of the generated password!
It will then take a few moments for your Neo4j Aura instance to be provisioned.
Sign in to Neo4j Aura:
Select "Create a new database" button.
Choose the "Free" tier.
Enter a name for your Neo4j Aura instance and select "Create database"
Step 1:
Step 2:
Step 3:
The Neo4j GraphQL Library
Overview
21
The Neo4j GraphQL Library
For building Node.js GraphQL APIs with Neo4j.
The fundamental goal of the Neo4j GraphQL Library is to make it easier to build GraphQL APIs backed by Neo4j.
Goals Of The Neo4j GraphQL Library
GraphQL First Development
GraphQL type definitions can drive the database data model, which means we don’t need to maintain two separate schemas for our API and database.
Goals Of The Neo4j GraphQL Library
Auto-generate GraphQL API Operations
With the Neo4j GraphQL Library, GraphQL type definitions provide the starting point for a generated API that includes:
Goals Of The Neo4j GraphQL Library
Generate Cypher From GraphQL Operations
To reduce boilerplate and optimize for performance the Neo4j GraphQL Library automatically generates a single database query for any arbitrary GraphQL request. This means the developer does not need to implement resolvers and each GraphQL operation results in a single roundtrip to the database.
Goals Of The Neo4j GraphQL Library
Extend GraphQL With Cypher
To add custom logic beyond CRUD operations, you can use the @cypher GraphQL schema directive to add computed fields bound to a Cypher query to the GraphQL schema.
Neo4j GraphQL Library Quickstart
Neo4j GraphQL Library Quickstart
Create index.js:
Neo4j GraphQL Library Quickstart
Start GraphQL server:
This will start a local GraphQL API and will also serve the GraphQL Playground IDE for querying the API or exploring documentation using GraphQL’s introspection feature.
Building An Online Bookstore GraphQL API
For the rest of the workshop we will be building
an API for an online bookstore.
First, we need to define our data model.
The graph data modeling process:
Setting Up Our Environment
Hands-On
Exercise
{
books {
title
}
}
Neo4j Aura Free Tier Setup
Let's create a Neo4j Aura Free instance that we'll use for the rest of the workshop and connect to our GraphQL API in CodeSandbox
Hands-On
Exercise
Update the Codesandbox .env file with your Neo4j credentials:
Once your Neo4j Aura instance is online you'll see the connection string (neo4j+s://xxxxx.databases.neo4j.io)
Be sure to take note of the generated password!
It will then take a few moments for your Neo4j Aura instance to be provisioned.
Sign in to Neo4j Aura:
Select "Create a new database" button.
Choose the "Free" tier.
Enter a name for your Neo4j Aura instance and select "Create database"
Step 1:
Step 2:
Step 3:
Step 4:
Neo4j Sandbox Setup
If you have issues with Neo4j Aura you can also use Neo4j Sandbox
Hands-On
Exercise
Update the Codesandbox .env file with your Neo4j credentials:
Take note of your Neo4j Sandbox Bolt URL and password
Step 1:
Step 2:
Step 3:
Defining A Property Graph Model With GraphQL
Defining A Property Graph Model With GraphQL
Schema Directives
The @relationship directive is used to define relationships.
DateTime and Point scalar types are available and map to the equivalent native Neo4j database types.
The @timestamp directive is used to indicate the property will be automatically updated when the node is created and updated.
The @id directive marks a field as a unique identifier and enables auto-generation when the node is created.
More on directives in the documentation.
Creating Data - Generated Mutations
mutation {
createBooks(
input: {
isbn: "1492047686"
title: "Graph Algorithms"
price: 37.48
description:
"Practical Examples in Apache Spark and Neo4j"
}
) {
books {
isbn
title
price
description
__typename
}
}
}
Creating Data - Generated Mutations
mutation {
createReviews(
input: {
rating: 5
text: "Best overview of graph data science!"
book: { connect: { where: { node: { title: "Graph Algorithms" } } } }
}
) {
reviews {
rating
text
createdAt
book {
title
}
}
}
}
Creating Data - Nested Mutations
mutation {
createCustomers(
input: {
username: "EmilEifrem7474"
reviews: {
connect: {
where: { node: { text: "Best overview of graph data science!" } }
}
}
orders: {
create: {
node: {
books: {
connect: { where: { node: { title: "Graph Algorithms" } } }
}
shipTo: {
create: {
node: {
address: "111 E 5th Ave, San Mateo, CA 94401"
location: {
latitude: 37.5635980790
longitude: -122.322243272725
}
}
}
}
}
}
}
}
) {
customers {
username
orders {
placedAt
books {
title
}
shipTo {
address
}
}
reviews {
text
rating
book {
title
}
}
}
}
}
Let's Clear Out The Database...
MATCH (a) DETACH DELETE a
Hands-On
Exercise
… and load some sample data via GraphQL
mutation {
createBooks(
input: [
{
isbn: "1492047686"
title: "Graph Algorithms"
price: 37.48
description: "Practical Examples in Apache Spark and Neo4j"
}
{
isbn: "1119387507"
title: "Inspired"
price: 21.38
description: "How to Create Tech Products Customers Love"
}
{
isbn: "190962151X"
title: "Ross Poldark"
price: 15.52
description: "Ross Poldark is the first novel in Winston Graham's sweeping saga of Cornish life in the eighteenth century."
}
]
) {
books {
title
}
}
createCustomers(
input: [
{
username: "EmilEifrem7474"
reviews: {
create: {
node: {
rating: 5
text: "Best overview of graph data science!"
book: { connect: { where: { node: { isbn: "1492047686" } } } }
}
}
}
orders: {
create: {
node: {
books: {
connect: { where: { node: { title: "Graph Algorithms" } } }
}
shipTo: {
create: {
node: {
address: "111 E 5th Ave, San Mateo, CA 94401"
location: {
latitude: 37.5635980790
longitude: -122.322243272725
}
}
}
}
}
}
}
}
{
username: "BookLover123"
reviews: {
create: {
node: {
rating: 4
text: "Beautiful depiction of Cornwall."
book: { connect: { where: { node: { isbn: "190962151X" } } } }
}
}
}
orders: {
create: {
node: {
books: {
connect: [
{ where: { node: { title: "Ross Poldark" } } }
{ where: { node: { isbn: "1119387507" } } }
{ where: { node: { isbn: "1492047686" } } }
]
}
shipTo: {
create: {
node: {
address: "Nordenskiöldsgatan 24, 211 19 Malmö, Sweden"
location: {
latitude: 55.6122270502
longitude: 12.99481772774
}
}
}
}
}
}
}
}
]
) {
customers {
username
}
}
}
Hands-On
Exercise
Querying With GraphQL - Query Fields
By default, each type defined in the GraphQL type definitions will have a GraphQL Query field generated and added to the Query type as the pluralized name of the type (for example the type Movie becomes a Query field movies). Each query field is an entry point into the GraphQL API. Since GraphQL types are mapped to node labels in Neo4j, you can think of the Query field as the starting point for a traversal through the graph.
Querying With GraphQL - Query Fields
The response data matches the shape of our GraphQL query - as we add more fields to the GraphQL selection set those fields are included in the response object.
Querying With GraphQL - Sorting & Pagination
A sorting input type is generated for each type in the GraphQL type definitions, allowing for Query results to be sorted by each field using the options field argument.
Offset-based pagination is available by passing skip and limit values as part of the options argument.
"Count queries" allow us to calculate the total number of pages.
Offset-Based Pagination
Querying With GraphQL - Sorting & Pagination
Cursor-based pagination can be used on relationship fields using Relay-style "Connection" types.
See the documentation for more details.
Cursor-Based Pagination
Querying With GraphQL - Filtering
Query results can be filtered using the where argument. Filter inputs are generated for each field and expose comparison operators specific to the type of the field. For example, for numeric fields filter input operators include equality, greater than (_GT), less than (_LT), etc. String fields expose the common string comparison operators such as _STARTS_WITH, _CONTAINS, _ENDS_WITH, etc.
Querying With GraphQL - Filtering (Nested)
We can also use the where argument in nested selections to filter relationships. Here we are filtering for reviews created after Jan 1, 2021 using the createdAt_GT filter input on the createdAt DateTime type, specifying the date using the ISO format.
Querying With GraphQL - Geo Distance
For Point fields we can filter results by the distance to another point. Here we search for addresses within 1km of a specified point
Querying With GraphQL - Filtering Using Relationships
Let’s look at an example that applies filtering at the root of our query, but using a relationship. Let’s say we want to search for all orders where the shipTo address is within 1km of a certain point. To do that we’ll use the where argument at the root of the query (in the orders Query field), but use a nested input to specify we want to filter using the shipTo relationship and the corresponding Address node.
Exercise: Updating The GraphQL Schema
Hands-On
Exercise
Title | Author(s) |
Inspired | Marty Cagan |
Ross Poldark | Winston Graham |
Graph Algorithms | Mark Needham, Amy E. Hodler |
Title | Subject(s) |
Inspired | Product management, Design |
Ross Poldark | Historical fiction, Cornwall |
Graph Algorithms | Graph theory, Neo4j |
If you get stuck you can find the solutions in the README.md file in this Codesandbox.
Adding Custom Logic
Cypher Schema Directive & Custom Resolvers
50
Setup
MATCH (a) DETACH DELETE a
Hands-On
Exercise
mutation {
createBooks(
input: [
{
isbn: "1492047686"
title: "Graph Algorithms"
price: 37.48
description: "Practical Examples in Apache Spark and Neo4j"
subjects: {
create: [
{ node: { name: "Graph theory" } }
{ node: { name: "Neo4j" } }
]
}
authors: {
create: [
{ node: { name: "Mark Needham" } }
{ node: { name: "Amy E. Hodler" } }
]
}
}
{
isbn: "1119387507"
title: "Inspired"
price: 21.38
description: "How to Create Tech Products Customers Love"
subjects: {
create: [
{ node: { name: "Product management" } }
{ node: { name: "Design" } }
]
}
authors: { create: { node: { name: "Marty Cagan" } } }
}
{
isbn: "190962151X"
title: "Ross Poldark"
price: 15.52
description: "Ross Poldark is the first novel in Winston Graham's sweeping saga of Cornish life in the eighteenth century."
subjects: {
create: [
{ node: { name: "Historical fiction" } }
{ node: { name: "Cornwall" } }
]
}
authors: { create: { node: { name: "Winston Graham" } } }
}
]
) {
books {
title
}
}
createCustomers(
input: [
{
username: "EmilEifrem7474"
reviews: {
create: {
node: {
rating: 5
text: "Best overview of graph data science!"
book: { connect: { where: { node: { isbn: "1492047686" } } } }
}
}
}
orders: {
create: {
node: {
books: {
connect: { where: { node: { title: "Graph Algorithms" } } }
}
shipTo: {
create: {
node: {
address: "111 E 5th Ave, San Mateo, CA 94401"
location: {
latitude: 37.5635980790
longitude: -122.322243272725
}
}
}
}
}
}
}
}
{
username: "BookLover123"
reviews: {
create: {
node: {
rating: 4
text: "Beautiful depiction of Cornwall."
book: { connect: { where: { node: { isbn: "190962151X" } } } }
}
}
}
orders: {
create: {
node: {
books: {
connect: [
{ where: { node: { title: "Ross Poldark" } } }
{ where: { node: { isbn: "1119387507" } } }
{ where: { node: { isbn: "1492047686" } } }
]
}
shipTo: {
create: {
node: {
address: "Nordenskiöldsgatan 24, 211 19 Malmö, Sweden"
location: {
latitude: 55.6122270502
longitude: 12.99481772774
}
}
}
}
}
}
}
}
]
) {
customers {
username
}
}
}
Adding Custom Logic To The GraphQL API
Custom Resolvers
@cypher GraphQL Schema Directive
52
52
Cypher GraphQL Schema Directive
Computed Scalar Field
With the @cypher schema directive in the Neo4j GraphQL Library we can add a field subTotal to our Order type that includes the logic for traversing to the associated Book nodes and summing the price property value of each book.
Here we use the extend type syntax of GraphQL SDL but we could also add this field directly to the Order type definition as well.The @cypher directive takes a single argument statement which is the Cypher statement to be executed to resolve the field. This Cypher statement can reference the this variable which is the currently resolved node, in this case the currently resolved Order node.
Cypher GraphQL Schema Directive
Computed Scalar Field
We can now include the subTotal field in our selection set to execute the custom Cypher query...
Cypher GraphQL Schema Directive
Node & Object Fields
In addition to scalar fields we can also use @cypher directive fields on object and object array fields with Cypher queries that return nodes or objects.
Let’s add a recommended field to the Customer type, returning books the customer might be interested in purchasing based on their order history and the order history of other customers in the graph.
Cypher GraphQL Schema Directive
Node & Object Fields
Now we can use this recommended field on the Customer type. Since recommended is an array of Book objects we need to select the nested fields we want to be returned - in this case the title field.
Cypher GraphQL Schema Directive
Field Arguments → Cypher Parameters
Any field arguments declared on a GraphQL field with a Cypher directive are passed through to the Cypher query as Cypher parameters. Let’s say we want the client to be able to specify the number of recommendations returned. We’ll add a field argument limit to the recommended field and reference that in our Cypher query as a Cypher parameter.
Cypher GraphQL Schema Directive
Field Arguments → Cypher Parameters
We set a default value of 3 for this limit argument so that if the value isn’t specified the limit Cypher parameter will still be passed to the Cypher query with a value of 3. The client can now specify the number of recommended books to return
Cypher GraphQL Schema Directive
Node & Object Fields
We can also return a map from our Cypher query when using the @cypher directive on an object or object array GraphQL field. This is useful when we have multiple computed values we want to return or for returning data from an external data layer.
Let’s add weather data for the order addresses so our delivery drivers know what sort of conditions to expect. We’ll query an external API to fetch this data using the apoc.load.json procedure.
First, we’ll add a type to the GraphQL type definitions to represent this object (Weather), then we’ll use the apoc.load.json procedure to fetch data from an external API and return the current conditions, returning a map from our Cypher query that matches the shape of the Weather type.
Cypher GraphQL Schema Directive
Node & Object Fields
Now we can include the currentWeather field on the Address type in our GraphQL queries.
Cypher GraphQL Schema Directive
Custom Query Fields
We can use the @cypher directive on Query fields to compliment the auto-generated Query fields provided by the Neo4j GraphQL Library. Perhaps we want to leverage a full-text index for fuzzy matching for book searches?
First, in Neo4j Browser, create the full-text index:
In Cypher we would search using the index like this:
CALL db.index.fulltext.queryNodes("bookIndex", "garph~")
CALL db.index.fulltext.createNodeIndex("bookIndex", ["Book"],["title", "description"])
Cypher GraphQL Schema Directive
Custom Query Fields
To take advantage of the full text index in our GraphQL API add a bookSearch field to the Query type in our GraphQL type definitions which requires a searchString argument that becomes the full-text search term
Cypher GraphQL Schema Directive
Custom Query Fields
And we now have a new entry-point to our GraphQL API allowing for full-text search of book titles and descriptions.
Cypher GraphQL Schema Directive
Custom Mutation Fields
Similar to adding Query fields, we can use @cypher schema directives to add new Mutation fields. This is useful in cases where we have specific logic we’d like to take into account when creating or updating data. Here we make use of the MERGE Cypher clause to avoid creating duplicate Subject nodes and connecting them to books.
Cypher GraphQL Schema Directive
Custom Mutation Fields
Cypher GraphQL Schema Directive
Custom Resolvers
Combining the power of Cypher and GraphQL is extremely powerful, however there are bound to be some cases where we want to add custom logic using code by implementing resolver functions. This might be where we want to fetch data from another database, API, or system. Let’s consider a contrived example where we compute an estimated delivery date using a custom resolver function.
First, we add an estimatedDelivery field to the Order type, including the @ignore directive which indicates we plan to resolve this field manually and it will not be included in the generated database queries.
Now it’s time to implement our Order.estimatedDelivery resolver function. Our function simply calculates a random date - but the point is that this can be any custom logic we choose to define.
Cypher GraphQL Schema Directive
Custom Resolvers
And now we can reference the estimatedDelivery field in our GraphQL queries. When this field is included in the selection instead of trying to fetch this field from the database, our custom resolver will be executed.
Exercise: Cypher Schema Directive
Hands-On
Exercise
Authorization
Adding Authorization Rules To Your API Using The @auth Directive
69
The @auth Directive
The Neo4j GraphQL Library provides an @auth GraphQL schema directive that enables us to attach authorization rules to our GraphQL type definitions. The @auth directive uses JSON Web Tokens (JWTs) for authentication. Authenticated requests to the GraphQL API will include an authorization header with a Bearer token attached. For example:
POST / HTTP/1.1
authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyLCJyb2xlcyI6WyJ1c2VyX2FkbWluIiwicG9zdF9hZG1pbiIsImdyb3VwX2FkbWluIl19.IY0LWqgHcjEtOsOw60mqKazhuRFKroSXFQkpCtWpgQI
content-type: application/json
JSON Web Token (JWT)
JWTs are a standard for representing and cryptographically verifying claims (a JSON payload) securely and are commonly used for authentication and authorization.
The @auth Directive
isAuthenticated
The isAuthenticated rule is the simplest authorization rule we can add. It means that any GraphQL operation that accesses a field or object protected by the isAuthenticated rule must have a valid JWT in the request header.
Let’s make use of the isAuthenticated authorization rule in our bookstore GraphQL API to protect the Subject type. Let’s say we want to make returning a book’s subjects a "premium" feature to encourage users to sign-up for our application. To do this we’ll make the following addition to our GraphQL type definitions, extending the Subject type:
The @auth Directive
isAuthenticated
Unauthenticated
Authenticated
The @auth Directive
Roles
Roles are the next type of authorization rule that we will explore. A JWT payload can include an array of "roles" that describe the permissions associated with the token.
The @auth Directive
Allow
A customer must not be able to view orders placed by other customers. Adding an Allow rule will allow us to protect orders from other nosy customers.
Here we add a rule to the Order type that a customer’s "sub" (the subject) claim in the JWT must match the username of the customer who placed the order.
The @auth Directive
Allow
Authenticated as user EmilEifrem7474
Authenticated as user BookLover123
The @auth Directive
Allow
Of course we will also allow admins to have access to orders, so let’s update the rule to also grant access to any requests with the "admin" role
The @auth Directive
Where
In the previous example the client was required to filter for orders that the customer had placed. We don’t always want to expect the client to include this filtering logic in the GraphQL query. In some cases we simply want to return whatever data the currently authenticated user has access to. For these cases we can use a Where authorization rule to apply a filter to the generated database queries - ensuring only the data the user has access to is returned.
We want a user to only be able to view their own customer information. Here we add a rule to the Customer type that will apply a filter any time the customer type is accessed that filters for the currently authenticated customer by adding a predicate that matches the username property to the sub claim in the JWT.
The @auth Directive
Where
Note that our query doesn’t specify which customer to return - we’re requesting all customers - but we only get back the customer that we have access to.
The @auth Directive
Bind
Bind allows us to specify connections that must exist in the graph when creating or updating data based on claims in the JWT.
We want to add a rule that when creating a review, the review node is connected to the currently authenticated customer - we don’t want customers to be writing reviews on behalf of other users! This rule means the username of the author of a review must match the sub claim in the JWT when creating or updating reviews
The @auth Directive
Bind
If a customer tries to create a review and connect it to a customer other than themselves the mutation will return an error.
The @auth Directive
@cypher Directive Fields
There are two ways to make use of authorization features when using the @cypher schema directive:
Let’s make use of both of those aspects by adding a Query field that returns personalized recommendations for a customer!
The @auth Directive
@cypher Directive Fields
In our Cypher query we’ll have access to a $auth.jwt parameter that represents the payload of the JWT. We’ll use that value to look up the currently authenticated customer by username, then traverse the graph to find relevant recommendations based on their purchase history. We’ll also include the isAuthenticated rule since we only want authenticated customers to use this Query field.
The @auth Directive
@cypher Directive Fields
Exercise: Authorization
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJCb2JMb2JsYXc3Njg3Iiwicm9sZXMiOlsiYWRtaW4iXSwiaWF0IjoxNTE2MjM5MDIyfQ.f2GKIu31gz39fMJwj5_byFCMDPDy3ncdWOIhhqcwBxk
dFt8QaYykR6PauvxcyKVXKauxvQuWQTc
Hands-On
Exercise
Other
Resources