6. 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.org
7. 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.
8. GraphQL Concepts - GraphQL Operations
Each GraphQL operation is either
a Query, Mutation, or
Subscription.
9. 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
10. 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
11. GraphQL Concepts - Selection Set
The response to a GraphQL operation matches the shape of the
selection set, returning on the data requested.
Selection set
12. 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
13. Benefits Of GraphQL
● Overfetching - sending less data over the wire
● Underfetching - everything the client needs in a single request
● The GraphQL specification defines exactly what GraphQL is
● Simplify data fetching with component-based data interactions
● "Graphs all the way down" - GraphQL can help unify disparate systems and
focus API interactions on relationships instead of resources.
● Developer productivity - By reasoning about application data as a graph with
a strict type system, developers can focus on building applications.
14. GraphQL Challenges
● Some well understood practices from REST don’t apply
○ HTTP status codes
○ Error handling
○ Caching
● Exposing arbitrary complexity to the client and performance considerations
● The n+1 query problem - the nested nature of GraphQL operations can lead to multiple
requests to the data layer(s) to resolve a request
● Query costing and rate limiting
Best practices and tooling have emerged to address all of the above, however it’s important
to be aware of these challenges.
15. 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.
16. GraphQL Tooling - GraphQL Playground
Open movies.neo4j-graphql.com
● Explore the "Docs" tab to learn more about the API schema
● Run these GraphQL queries:
Hands On
Exercise
{
movies(options: { limit: 10 }) {
title
actors {
name
}
}
}
{
directors(where: {name:"Robert Redford"}) {
name
directed {
title
plot
}
}
}
● Try modifying the query selection set to return additional fields
○ Try using ctrl+space for auto-complete
○ What can you find?
18. 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:
dev.neo4j.com/aura-login
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:
20. 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.
21. 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.
22. 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:
● Query & Mutation types (an API
entrypoint for each type defined in
the schema)
● Ordering
● Pagination
● Complex filtering
● DateTime & Spatial types and
filtering
23. 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.
24. 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.
27. 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.
28. 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:
1. Identify entities → Nodes
2. What are the attributes of these entities? → Properties
3. How are these entities connected? → Relationships
4. Can you traverse the graph to answer the business
requirements of your application?
29. Setting Up Our Environment
● Open this Codesandbox
● Add your Neo4j Aura connection details to the .env file (NEO4J_URI,
NEO4J_USER, & NEO4J_PASSWORD environment variables)
○ You will need to sign in to Codesandbox to save your updates
● In GraphQL Playground (running in Codesandbox), run the following GraphQL
query (you'll have an empty result set, but shouldn't see any errors):
Hands-On
Exercise
{
books {
title
}
}
30. 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:
dev.neo4j.com/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:
31. 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
Sign in to Neo4j
Sandbox:
dev.neo4j.com/sandbox
Select "Blank
Sandbox"
Select "Launch Project"
Step 1: Step 2:
Step 3:
33. Defining A Property Graph Model With GraphQL
Schema Directives
The @relationshipdirective 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.
34. 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
}
}
}
35. 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
}
}
}
}
39. 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.
40. 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.
41. 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.
Querying With GraphQL - Sorting & Pagination
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
42. 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
43. 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.
44. 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.
45. 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
46. 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.
47. Exercise: Updating The GraphQL Schema
● Update schema.graphql adding Author and Subject types to our
GraphQL schema
● Once updated, write GraphQL mutations to add authors and subjects to
the graph:
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.
50. 50
Adding Custom Logic To The GraphQL API
Custom Resolvers
● Implement field resolver
function with your custom logic
● Resolver function will be called
after initial data is fetched from
Neo4j
@cypher GraphQL Schema
Directive
● Add custom Cypher statements
to the GraphQL schema
● Single Cypher query is
generated / one round trip to
the database
50
51. 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.
52. Cypher GraphQL Schema Directive
Computed Scalar Field We can now include the subTotal
field in our selection set to
execute the custom Cypher
query...
53. 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.
54. 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.
55. 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.
56. 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
57. 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.
58. Cypher GraphQL Schema Directive
Node & Object Fields
Now we can include the
currentWeather field on the
Address type in our GraphQL
queries.
59. 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:
CALL db.index.fulltext.createNodeIndex("bookIndex", ["Book"],["title", "description"])
In Cypher we would search using the index like this:
CALL db.index.fulltext.queryNodes("bookIndex", "garph~")
60. 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
61. 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.
62. 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.
64. 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.
65. 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.
66. Exercise: Cypher Schema Directive
● The similar field on the Book type returns recommended
books.
● How could you modify and improve this Cypher query to find
similar books?
Hands-On
Exercise
68. 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.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MD
IyLCJyb2xlcyI6WyJ1c2VyX2FkbWluIiwicG9zdF9hZG1pbiIsImdyb3VwX2FkbWluIl19.IY0LWqgHcjEtOsOw60mqKazhuRFKroSXFQkp
CtWpgQI
content-type: application/json
69. 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.
70. 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:
72. 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.
73. 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.
75. 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
76. 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.
77. 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.
78. 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
79. 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.
80. The @auth Directive
@cypher Directive Fields
There are two ways to make use of authorization features when using the
@cypher schema directive:
1) Apply the authorization rules isAuthenticated and roles using the @auth
directive.
2) Reference the JWT payload values in the Cypher query attached to a
@cypher schema directive.
Let’s make use of both of those aspects by adding a Query field that returns
personalized recommendations for a customer!
81. 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.
83. Exercise: Authorization
● Open this Codesandbox which includes the authorization rules defined
above
● Using this admin token create a new user and an order for this user
(be sure to include at least one book in the order!):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJCb2JMb2JsYXc3Njg3Iiwicm9sZXMiOlsiYWRtaW4iXSwiaWF0IjoxNTE2MjM5MDIyf
Q.f2GKIu31gz39fMJwj5_byFCMDPDy3ncdWOIhhqcwBxk
● Generate a JWT token for your new user using jwt.io
○ Be sure to use this JWT secret when creating the token:
dFt8QaYykR6PauvxcyKVXKauxvQuWQTc
● Next, use this token to add a review for the book purchased by this
user.
● Finally, write a query to view the customer’s details, including their
order history and their reviews.
Hands-On
Exercise
84. Other
● Working with unions & interfaces
● The Neo4j GraphQL OGM
● Working with relationship properties
● ...