This workshop will explore how to build GraphQL APIs backed Neo4j, a native graph database. The Neo4j GraphQL Library allows developers to quickly design and implement fully functional GraphQL APIs without writing any resolvers. This workshop will show how to use the Neo4j GraphQL Library to build a Node.js GraphQL API, including adding custom logic and authorization rules.
Table of contents:
- Overview of GraphQL and building GraphQL APIs
- Building Node.js GraphQL APIs backed a native graph database using the Neo4j GraphQL Library
- Adding custom logic to our GraphQL API using the @cypher schema directive and custom resolvers
- Adding authentication and authorization rules to our GraphQL API
Building GraphQL APIs With The Neo4j GraphQL Library
This workshop will explore how to build GraphQL APIs backed Neo4j, a native graph database. The Neo4j GraphQL Library allows developers to quickly design and implement fully functional GraphQL APIs without writing any resolvers. This workshop will show how to use the Neo4j GraphQL Library to build a Node.js GraphQL API, including adding custom logic and authorization rules.
AI Generated Video Summary
Today's workshop focused on building GraphQL APIs using Neo4j, a graph database. The Neo4j GraphQL library was used to generate the API, handle data fetching, and solve the N plus one query problem. Custom logic and authorization rules were added using Cypher statements and the auth schema directive. The workshop also covered filtering, sorting, and pagination options in GraphQL, as well as the integration of Neo4j with Code Sandbox and Neo4j Aura DB. Overall, the workshop provided a comprehensive overview of building GraphQL APIs with Neo4j and highlighted the benefits and challenges of using this technology.
1. Introduction to Neo4j and GraphQL
Hello everyone! Today, we'll be building GraphQL APIs for sandboxing using Neo4j, a graph database. We'll start by discussing Neo4j and GraphQL, then explore the Neo4j GraphQL library for building APIs. We'll also cover adding custom logic and authorization rules to our API. Please note that we'll be focusing on the backend piece and not integrating the GraphQL API into a frontend application.
ReactJS, through the mouth of assistant projects and project managers. Well, hello everyone. Thanks for joining today. We are going to be building GraphQL APIs for sandboxing, which is a hosted database as a service. You don't need to set up local environments. We're going to do everything with these hosted sandbox environments. So don't worry about setting up a local environment, but we'll talk about that in a little bit.
The slides are linked at dev.neo4j.com slash GraphQL dash training, link in the chat here. I would recommend pulling up these slides as we work through this. There are several links and code snippets and things like that, that you may want to grab from the slides. So I'd recommend pulling up the slides as we go through this and just having them open in a tab. There's also the Discord, which Lara linked at the beginning. So I'll monitor both the Zoom chat and Discord as well. So that's either one. If you have questions, please just drop them in the chat, either in Zoom or Discord, and I'll try to monitor both and share links in both.
2. Neo4j Aura DB and Code Sandbox
Today, we'll be using Neo4j Aura DB and Code Sandbox for our GraphQL API development. Neo4j Aura offers a free tier for hosting Neo4j instances in the cloud, and Code Sandbox allows us to run code in the browser without dealing with local development issues. We'll start with a Skeleton Starter Code Sandbox and work through hands-on exercises. Feel free to ask questions in the chat.
3. Neo4j and GraphQL Basics
Neo4j is a graph database that uses a property graph data model. It allows us to store arbitrary key-value pair properties on nodes and relationships. The query language used in Neo4j is called Cypher, which is focused on pattern matching. We'll be using GraphQL to generate Cypher queries. Graph databases offer interesting implications such as graph analytics and data visualization. They also integrate with various application architectures. In this workshop, we'll focus on building the API layer using GraphQL.
Great, so let's talk a little bit about Neo4j before we dive into building GraphQL APIs. So I think most folks said that Neo4j was new to them, which is great. I guess maybe what database technologies are folks familiar with? Have most folks used relational databases or maybe Mongo, let us know in the chat. If you're familiar with like relational databases or document databases, graph databases are similar concept, right? So, this is where we are storing, modeling, querying our application data. The difference with a graph database, like Neo4j, I should say, one of the first differences that you notice is that the data model is not tables, not documents. A data model is a graph. And when you say graph, we're talking about nodes. These are the entities in our data and relationships, connect them. Specifically with Neo4j, we use the property graph data model, which we'll talk about a little bit. Basically what that means is that we can store arbitrary key value pair properties on nodes and relationships.
With Neo4j, we use a query language called Cypher. You can think of Cypher like SQL, but for graphs. Cypher is very much focused on pattern matching. There's an example Cypher statement in the upper right of the screen there in this first line, where it says match, and then we have this graph pattern. Match is like a statement that's saying, it's like a statement that's saying, find some data in the graph where this pattern exists. And then the pattern, we draw this sort of ASCII art representation of a graph. So we have address in parentheses, those parentheses sort of like drawing a circle, that's a node. So we're saying find address nodes with an incoming registered address relationship. So you see how we're sort of drawing that arrow in brackets that's connected to an officer node, and then traversing out from that officer to entity nodes, filtering where the address contains New York. So this cipher statement is saying, find address nodes where the address is in New York, and find officers that have an address in New York, and then what entities are they connected to? This dataset comes, this query comes from the Panama Papers or the Pandora Papers dataset, which is a data journalism investigation that used Neo4j to make sense of complex offshore legal entity structures in like offshore banking. So we'll do a little bit with cipher today. Mostly, we're gonna be focused on using GraphQL and generating cipher queries from GraphQL. So we will write a little bit of cipher when we get to the custom logic section. But if you haven't seen cipher before, just wanted to show some of the concepts there this idea of graph pattern matching. So there are lots of interesting implications of using a graph database instead of like a document database or relational database. One is that we can do lots of interesting graph analytics with graph databases, because we can model our data differently and that allows us to, I think, write these very interesting queries that are sort of the equivalent of writing lots of joins and in SQL just by sort of expressing this graph pattern which was quite neat. There are also tools for data visualization which we might touch on a little bit today. And then we have lots of sort of integration. So I guess if we think of the database as being sort of the code, database touches a lot of different pieces of application architecture right? So database kind of sits at the core of our architecture but we need to be able to move data back and forth and there are lots of different sort of use cases that we may have for using a database in the first place. So from the spectrum on the right where we're talking about things like analytics and data science use cases. And then over on the left end of the spectrum where we're talking more about like operational, transactional applications that we're building. Maybe we're building like a social network or an e-commerce sites or something like that where we want more transactional operational workloads. And we're interested in things like building an API layer that's gonna sit between the client and the database and thinking about the tools we're gonna use there. So for our workshop today, we're very much on this left end of the spectrum where we are interested in building the API layer for a client application. And in this case, we're going to focus on doing that in GraphQL.
4. Neo4j Browser and Cypher Queries
Today, we'll be using code sandbox environments and the Neo4j GraphQL library to work on the backend piece. We'll focus on the backend and not the frontend aspect. If you're interested in a full stack application, you can check out the GRANDstack starter project. Let's now explore a hands-on example using Neo4j Browser and Cypher queries. We'll work with a dataset of news articles and learn how to write queries to find recent articles based on labels and properties.
Cool, so Anthony says, familiar with SQL Server Firebase and Mongo. Great, cool. So now you can add Neo4j to that list after today. So here's some resources. The slides are probably the most important one. So there's a link for the slides in the chat, dev.neo4j.comslashgraphql-training. Let me just drop those in the chat one more time. I know sometimes chat kind of disappears and make a pain to scroll through. So that is not a clickable link. Let's try again. Dev.neo4j.comslashgraphql-training. Cool.
So like I said, we're gonna use these code sandbox environments and there's a specific link for each one. So if you open up the slides, at least have them in a tab to refer to. It'd be helpful just to grab the links and some of the code snippets and things as we work through the exercises. All the code for today is also linked here. Dev.neo4j.comgraphql-training-code. And these are the GraphQL queries that we're going to work through in some of our examples. So this might be a good one to bring up as well. And then the other things in here, so we're going to be using the Neo4j GraphQL library. The documentation is linked here. There's an overview page for the GraphQL library, it's links to example apps and other resources as well as a self-paced training that goes into a lot more detail than what we'll cover today.
So the timing for today, I should have said this is a three hour session and we're 20 minutes in. So that is the plan for today. Like I said, we're going to focus on the backend piece of this, not so much the front end aspect of this. So if you're interested in seeing how the pieces fit together for like a full stack application, including how the front end fits in. There's a starter project for what's called GRANDstack. That's GraphQL, React, Apollo, Neo4j database. So this GRANDstack starter is a good place to see just sort of how the pieces fit together in the context of a full stack app. But that's not going to be our focus today. Today we're focused on just the backend piece. Cool, so we talked a bit about Neo4j, I think maybe let's look at a more hands-on example. I'm going to bring up this one. Let's look at this example. This should be public. Let's see, I'll share the link, if I can remember the password here. I think news graph is one of the users. I'm going to add that. Yeah, so this is optional, I'm going to go through a couple of queries in Neo4j Browser here. So if you want to follow along, I'll drop a link in the chat and then the username is newsgraph and the password is also newsgraph. So this is Neo4j Browser, this is like a query workbench for Neo4j. So I write cipher queries and then I get back the results either like in a visual environment, but this is kind of the common development environment for working with Neo4j. I have data loaded in here from the New York Times API. So this is a data set news articles. So I think it's continuously running to scrape the most shared news articles each day. So we have things like articles, articles refer to topics or they're about people organizations refer to some geographic region and they have photos. So I can run db.schema.visualization to see, let's reset our styling here, there we go. So I can run this to see what my data model is. So I can see the nodes that I have and how those nodes are connected. So articles are about organizations or about a geographic region, about people. They have photos and topics. So I can write Cypher queries. Like let's say I want to find the most recent articles. I can write a Cypher statement using this graph pattern matching concept. So the pattern for this one is quite simple. I'm saying just find nodes with the label article. So when we say article nodes, what we're saying is find nodes that have the label article. So label is like a way to group nodes in the database, similar to like a table or like a collection from a document database is same conceptual level to think about these, I guess. Then we have these key value pair properties. So each article has a title, the date it was published, the URL for the article, and then kind of a description. So when we're thinking in GraphQL terms, what we're gonna do is map nodes and node labels, specifically, I guess, to types in GraphQL and how they're connected. Of course, we can write much more complex patterns. So for example, we could say, do we have any example queries in here, let's see. We've already written some interesting ones. Nope, that's fine. Let's say, let's find articles that have the topic, let's say exercise. My arrow's going the wrong way, goes this way. And again, you can, don't worry about following along. We're gonna start our Aura instance. But if you'd like to, I did link the username and password for this database.
5. Neo4j, GraphQL, and Resolver Functions
In this query, we find the topic with the name exercise and traverse along the has topic relationship to find connected article nodes. We can visually explore the articles and their related topics. GraphQL is an API query language and runtime used to define available data and relationships. Clients can specify a traversal through the data graph and the desired data to be returned. GraphQL operations are queries or mutations starting from an entry point on special types. Type definitions define the available data using a schema definition language (SDL) and directives. Resolver functions fetch data from the data layer, such as a database or other APIs.
6. Addressing the N plus one query problem in GraphQL
We can address the N plus one query problem in GraphQL by generating a single database query using the Neo4j GraphQL library. This eliminates the need for implementing resolvers and reduces the overhead of multiple round trips to the data layer. The library takes care of fetching data from the database, and the root-level query is generated automatically.
And we're using this sort of like ORM API here, assuming that we have that available for querying our database. So in this case, we would start off searching for a session with some query string and we go to the database and say find all the sessions by the search term and that would come back to our GraphQL server and then we would sort of iterate through that selection set and see, oh, well the user also asked for what room these sessions are in. So the room resolver would then go back to the database for each session that matched our search string, say here's the room that it's in and then do a similar thing for theme and for recommended. So you can see that we're doing these in sort of a nested fashion, right? So it's possible that for a GraphQL operation, we may make multiple round trips to our data layer. This is called the N plus one query problem, which is a common thing that comes up in GraphQL, which is that we have the possibility to be making multiple round trips to the data layer for a single GraphQL request. And this can be problematic for performance reasons, right? Like it's incurring some overhead to go to the database, to go to the data layer each time. It would be best if we could, in one operation, go to the database and say, here's all the data that I need for this GraphQL operation. And that comes back. So this N plus one query problem, this is something that comes up. There are different ways to address this. There's the data loader pattern, which allows us to do some batching and caching of objects. What we're going to do today with the Neo4j GraphQL library is see that we can actually generate a single database query. So in our case, this will be a single cipher query at the root level for any arbitrary GraphQL requests. So what that means is that we won't actually be implementing resolvers, instead, we're going to let the Neo4j GraphQL library take care of that for us, and that gives us a couple of advantages here. One is we don't have to write this kind of boilerplate syntax for fetching data from the database, instead, the library's gonna take care of that for us. And then we also don't have to worry about the N plus one query problem because this means that our database query is gonna be generated for us at the root level, which is quite nice.
7. Challenges and Considerations in GraphQL
GraphQL provides benefits in terms of addressing over-fetching and under-fetching, allowing us to send only the necessary data to the client. However, it also presents challenges in error handling, caching, and query costing. These challenges require different approaches compared to REST APIs. It's important to be aware of these challenges and utilize the available tooling and best practices.
So you may have seen the benefits of using GraphQL. This idea of addressing over-fetching and under-fetching where we're not sending too much data over the network, we're getting exactly the data that the client needs to render a view. We don't have to make multiple requests like we may in a REST API for accessing different resources. We can just add more things to our selection set to give us the data that we need in our application. And then some of the challenges that come up, we talked about the N plus one query problem. But in my mind, I think the way to think of most of these challenges of GraphQL are that a lot of the practices that come from the REST world don't cleanly apply in the GraphQL world. So things like error handling is a bit different, right? We can have errors at different levels in our selection set. So we need to think of error handling a bit differently. Caching, similarly, GraphQL queries can be kind of arbitrary. So you can't just cache a certain endpoint or a certain URL like we may be able to in the REST world. And then also things like how do we handle query costing and rate limiting again, related to this idea of arbitrary complexity of GraphQL queries. So these are some of the challenges that come up there. There's tooling and best practices that address all of these, but I think these are just important things to be aware of that may come up.
8. Using GraphQL Playground
We'll be using GraphQL Playground today to query and introspect GraphQL APIs. Take a few minutes to explore the docs tab, write some GraphQL queries, and get familiar with the Playground. Use control space for autocomplete suggestions and add fields to your queries. If you have any questions or issues, let us know in the chat.
So we're gonna use GraphQL Playground today. GraphQL Playground is a, I guess in browser tool for querying and introspecting GraphQL APIs. You may have seen Graphical or Apollo Studio has something called, is it called Explorer? I think that's built into Apollo Studio that in actually the Apollo, the linking out to Apollo Studio is now, I think the default with Apollo Server. These, these tools are similar. I like to use GraphQL Playground because we can just sort of host GraphQL Playground at our Graphical endpoints. So that's what we're going to use today. And we'll take a look at Playground in a minute. Actually right now, if we will.
So for this hands-on exercise piece, what I would like everyone to do is go to movies.near4j-graphql.com, and you will see GraphQL Playgrounds at that URL. And let's all take a few minutes to explore the docs tab, to see what the schema looks like, and then write some GraphQL queries. Just to get familiar with the way that, for example, our limit arguments work. So here we're looking for the first 10 movies, grabbing some information, then we're also filtering for directors. So I'm going to go to movies.neo4j-graphql.com. I'll drop a link to that, both in Zoom and in Discord. So you should see something like this. So in the right, we have the docs tab. So this allows us to inspect the API. We can see the query entry points, we can see the fields that are available on each type. And we can write some GraphQL queries. So note that it's quite nice because of this idea of introspection, we can do control space to get this autocomplete suggestions, so we can add fields. So here's, now we're adding the bio, the biography of each director, which is quite nice. So anyway, let's take a few minutes. Everyone can write some GraphQL queries and familiarize yourself with GraphQL Playground if you haven't seen it before, looking in the docs tab and using control space for autocompletes, add some fields to these queries and copy and paste these, I'll share the queries actually in discord. That's easy enough to do. There's the first query in discord and I'll drop the link for this other one in discord as well, so you can just copy and paste those. Great, so we'll pause here for just a couple minutes to give everyone a chance to get familiar with GraphQL Playground and this API and then we will pick up again. If you have any questions or issues, definitely just let us know in the chat. We'll pause for just a couple of minutes, so if you need a break, we will start up again in a couple of minutes.
9. Sorting Options and Building Our Own GraphQL API
Let's take a look at our sorting options for movies. We can sort by title and either ascending or descending order. We can also sort by year. To build our own GraphQL API, we'll use Neo4j Aura. Sign into Aura, create a database, and choose the free tier. Copy the password provided. The Neo4j GraphQL library allows us to build graphical APIs backed by Neo4j. It supports GraphQL-first development and provides directives to configure the API and add custom logic. Let's get more hands-on with some code and build a GraphQL API.
10. Neo4j GraphQL Library and Custom Logic
So another thing that the Neo4j GraphQL library does for us is basically takes our GraphQL and then generates a GraphQL API that has CRUD operations, so create, read, update, delete operations for each type that we have defined. So in GraphQL parlance, these are things like generating the query and mutation types. for the API, adding arguments for things like ordering and pagination, complex filtering, also adding support for working with things like the date time, like the temporal types and the spatial types that are available in the database. And then also, some things for aggregations and more complex operations. All those are generated for us. We don't have to add those explicitly to our schema.
Then at query time, for any arbitrary GraphQL operation that we send, a single database query is generated for us by the Neo4j GraphQL Library. So that means we don't have to write these resolver functions. The library takes care of generating the database for us. We don't really need to think about that. Think about, in this case, writing Cypher queries to build our GraphQL API. We don't need to do that. We only need to think about Cypher when we get to thinking about things like adding custom logic. This is really nice for developer productivity. We can get a GraphQL API up and running back by a database basically just by writing some type definitions. And then the performance aspect. This solves the n++ query problem for us by generating a database query and sending it to the database to figure out how to optimize. And Graph databases like Neo4j are really good at traversing the data graph. That's what Graph databases are optimized for. So, oftentimes, in GraphQL, we end up creating these queries that have a very nested structure, a very nested selection set. And so Graph databases are very good at traversing that graph. In a database implementation terms, what's going on there, there's something called index-free adjacency, which means that in Neo4j, in the database, we can traverse from one node to any other node that node is connected to, so traversing along relationships. We can do that without using an index. So in a relational database, to do the equivalent of that, which would be a join, we're using an index to see where two tables overlap, so a set operation. And that has to use an index, which means that that slows down as the size of those tables grows. But in Neo4j, the traversal, so the equivalent of a join, we're basically just chasing pointers, going to an offset, which is something that computers are very fast at, so it's more of a constant-time operation that's only dependent on the number of relationships that we're traversing, not on the overall size of the data. So as we add more data to the graph, our traversals from one node to another stay the same performance for those. So that's under the hood what's going on, and that's why graph databases are really good for traversing relationships.
Okay, so that's one of the goals of the Neo4j GraphQL library. The other is to expose custom logic through Cypher. So we said that the library generates CRUD operations based on the type definitions. We don't have to write any Cypher, any data-fetching logic. We just have to basically write our type definitions. That's great for basic CRUD operations. But what about when we have custom logic? Well, and for that, we can use Cypher statements in our GraphQL type definitions using the Cypher schema directives. So schema directives, these are like annotations in our GraphQL API, in our type definitions that are basically indicating that some custom logic should occur. So it's saying that there's some custom logic that needs to happen server side. It allows us to basically extend things in GraphQL. So this Cypher schema directive, this is like an annotation that we would add to our type definitions. It takes a single argument, which is a Cypher statement. So in this example, we're adding a recommended field, I think on a on the business type. So this is from an application that has businesses and user reviews of businesses. Think of something like I'm searching for restaurants and I want recommended restaurants. So I know a restaurant that I like. Here are other businesses, other restaurants that you may like as well. So the Cypher statement here is finding users who have reviewed the business that we're currently resolving and then looking at other businesses that those users have reviewed and using the number of overlaps as as a score for the recommendation. So this is a what's called a collaborative filtering recommendation query where we're using information from other users in the network to generate personalized recommendations. This is a a fairly basic example of collaborative filtering, but I think the idea applies for more advanced cases. But this this kind of traversal is very expressible in Cypher and graph databases. So now we're able to add this recommended field to our business type to the client of our API. They don't know that this is a custom field with this logic defined by Cypher. They just see a recommended field that is giving them an object array of business objects. So, I think this is my favorite feature of the Neo4j graphical library. This is super powerful because what this means is that we can use any functionality that we have in Cypher. We can expose that into our graphical API, which is super powerful. You'll notice, I guess, one thing that is interesting, which we have this keyword here. This is referring to the currently resolved business object in this case. So that's kind of like a special variable that gets injected into the Cypher statement.
11. Defining Type Definitions and Data Model
Then we define some tight definitions. This is just looking at movies and genres. And notice here that we're using this, relationship schema directive. So we said that in the property graph model, every relationship has a direction and a named type. GraphQL doesn't have that same concept for relationship fields. So we need to be able to encode that data because we're driving the database model from these type definitions. So we need to be able to specify that data to the type definition. So we need to be able to specify that data somewhere. So we do that with this relationship schema directive to specify the type and the direction.
Okay, that's it. Great. Well, let's get hands-on with an example in CodeSandbox. What we're going to do for the rest of the workshops, we've got what an hour and 45 minutes left. So what we're gonna do for the rest of the workshop the GraphQL API for an online bookstore. So the first thing that we would typically go through this graph data modeling process. Now, someone has already done this for us in this case, we're gonna be starting off with existing schema. But I think it's helpful to talk about how we get to the types, how we get to the data model that we ended up with. And I think of this graph data modeling process is kind of this iterative process where the first thing we do is identify what are the entities in our business requirements, essentially. So we start off with some business requirements that are like, I need to as a user, search for books by like a search string or search for books by subject. As a user, I want to be able to add books to an order. As a user, I want to be able to submit a review of a book, these sorts of things. Our business requirements, we go through identify the entities, think about what are the properties that we're storing. So books have an ISBN, which is basically like an ID field. They have a title. How are these entities connected? And so how do those become relationships? And then sort of draw out the graph model. And then the next part is to think of the questions that we have from our business requirements. So maybe I need to view all of the books in an order for a specific customer. And I think, well, can I traverse the graph to answer that question? Well, can I go from a customer to an order, from the order node I can traverse out to the books contained in the order? So yes, I can answer that question by traversing the graph. This way, I go through each of those for my business requirements. And then if I can answer all of the questions that I want to answer, then I'm done. My graph model is ready to go. If not, then I may need to iterate on that a bit. So I think of this as a very iterative process. So this is the data model that we are going to use today. So we have books. Books have a title, price, and description. Books are about a subject. We have authors. There are authors of books. We have orders that contain books and ship to an address. Customers can place an order and customers can also write reviews that are connected to a book. So this data model, by the way, we use this tool called Arrows. So Arrows.app. I'll drop a link to in the chat and Discord. We use this tool Arrows to, basically diagram our data model. So fundamentally Arrows is a graph diagramming tool, but it's very convenient. It allows us to express the property graph model. So for example, here's, this isn't exactly what we're working with. It's from a similar project, but it's very similar. So we can drag out nodes, so maybe user wrote a review. And then, well, review needs to be connected to a book. So we can update our model this way. And then we can check this in to version control. We can export say like the JSON for this and check that into version control, which is quite nice. We can also export GraphQL type definitions.
12. Exporting Data Model as GraphQL Type Definitions
We can export our data model from Arrows as GraphQL type definitions, making it easy to generate a GraphQL API using the Neo4j GraphQL library. This allows us to quickly go from diagramming our data model to having a fully functional GraphQL API.
I don't know why that looks weird. Label book. We can also export GraphQL type definitions. Although it doesn't think I have a label on book for some reason it's there. Let's delete our... Oh, I had a duplicate. Nice. Book. So we go through this data modeling process in Arrows and we can export these as, Oh, I need at least one property. So book, let's give it a title. There we go. So now, we're generating GraphQL type definitions that we can just copy and paste and hand those off to the Neo4j GraphQL library from just this diagram. So this is really nice. It allows us to quickly go from diagramming our data model to generating GraphQL type definitions, basically copy and paste into some template code. And we have a GraphQL API, which is super powerful.
13. Setting up Code Sandbox Environment
To set up our code sandbox environment, we'll start with a specific code sandbox link provided in the chat. After forking the code sandbox and signing in, we'll configure the environment to connect to our Neo4j Aura instance. The code editor will resemble the getting started example, with additional packages imported. We'll replace the environment variables with our Aura instance connection credentials. Then, we'll instantiate the Neo4j.graphql class and create a driver instance. The executable schema object is handed off to Apollo server, and our GraphQL API is started. We can test the API by running queries in the GraphQL Playground. Let's take a moment to open the code sandbox link, fork it, update the.env file with our Aura connection credentials, and run a simple query to ensure everything is set up correctly.
14. Adding Data with GraphQL Mutations
Let's pause for a few minutes to ensure everyone is set up and connected to their Neo4j Aura instance. We'll then discuss the type definitions and directives used in our GraphQL schema. After that, we'll explore how to add data to our database using GraphQL mutations. Let's switch to Code Sandbox to see the generated cipher and start by creating a single book with a randomly generated ISBN number. We'll use the 'create books' mutation and the 'book create input' type. For example, let's create a book titled 'Graph Algorithms' with the ISBN number 3748.
Let's pause for a few minutes. You're gonna chance to go through that. If you get any errors or have any issues, definitely let us know in the chat and we will get that figured out because we do wanna be connected to our Neo4j Aura instance going forward because next we're going to see how to add some data with GraphQL mutations. And then we'll need to add some things to our type definitions that we left out. So we'll see how to add some types and fields in our GraphQL API.
Okay. How is everyone doing? Does anyone have their code sandbox set up, working, connected to their Aura instance? Is anyone getting some errors? Okay. Not then I think we can move on. Okay, well I don't see anyone complaining in the chat that they had errors. So, I'm gonna assume that we all got our code sandbox up and running and we're connected to our Aura instance and we are ready to move on. Cool. So this is, yeah, so this is the steps that we want to go through. If we didn't provision our Aura Instance previously, we definitely want to grab the connection string from the Aura Console here. So this is the connection string for our database. And then when we first spin up or first provision the Aura instance gives us the password, which we're going to take both of those, put those in this dotenv file in our code Sandbox to get our GraphQL API connected to our Aura instance. Cool, so hopefully that's where everyone was able to get to. Let's talk a bit about the type definitions that we're working with. So if you look at the schema.graphql file, in our code Sandbox, so that's this file here. You can see that these type definitions correspond to the graph data model that we came up with earlier when we went through this graph data modeling process of thinking about what are the entities? How are they connected? This sort of thing. There's a couple of interesting things that I wanna point out. One is the relationship GraphQL schema directive, which we mentioned before. We used to specify the direction and the type of the relationship. But you'll also see some other directives that we're using. So the timestamp directive, which we have on the order playlist at field. So playlist at is of type date time. So first of all, we're using date time. We also are using this point type. These are added by the Neo4j GraphQL library to expose of temporal and spatial types that are available in the database. So point, date time, local date time, date, these kinds of things. Timestamp though is a directive that we can use to indicate that we want this value to be automatically generated for us in the server. So when we create an order, this is saying set the place that property to be the current date time. We don't want the client to be responsible for that. We don't want the client to say, yes, this is the current time that can be. A pain for the client to do and also be a security vulnerabilities. We want just to set that on the server. The ID directive is similar that's saying, well, this is the field we want to use to identify a uniqueness, but also we want a UUID to be generated for us automatically when this order object is created. So generate a random unique ID, set that as the order ID value. The client doesn't need to be responsible for that. So these are some examples of the way that we're using directives to configure the schema that we're creating. There are a few more directives. If we look in the documentation, I'll drop a link to this in the chat also. This is the documentation page for the Neo4j GraphQL library, which is helpful to have up as we're working through this. But you can see here there's quite a few directives that we use for configuring our schema. So for example, the auth directive, which we'll be using quite a bit in a moment, allows us to add authorization rules to our schema.
Okay, so that's directives. Let's take a look at adding some data to our database using GraphQL mutations. I'm gonna switch to Code Sandbox here. And hopefully that's big enough to see. I want to do everything in Code Sandbox here so that we can see the generated cipher. I think that is useful to sort of reason about. Okay, so what do we wanna do? We want to create some data in our database using GraphQL mutation. So first of all, if we look at the Docs tab, so far the examples we've been using have all been query operations, but we need to start with creating some data. So we can see we have create, delete, update, mutation generated for each type that we've defined. Here's the one for orders, orders takes, create orders takes an input of order create input type. And we can see it takes things like the shipping costs, the customer books, which can be used to connect in this case to existing books or to create new books. So you can see actually some fairly sophisticated things we can do here. If we think about these different operations that we have available in this input object. But let's start simple. Let's start with creating a single book. So we're going to look at the mutation. so mutations create books. So this book create input is what we're going to be working with. So we'll say mutation create books, takes an input object. Actually, this is an array of input objects. We could create multiple books in one operation, but we'll just do one. So book is going to have an ISBN number. We'll just make this up. So ISBN, this is like the, I don't know, like the ID of the book in some international format. Let's create our book graph algorithms. That is available for 3748.
15. Creating a Book and Review in Neo4j
We create a book called graph algorithms and verify it in Neo4j browser. Then, we create a review for the book, connecting it to the existing book using filtering. The review node has properties such as rating, text, and a created at timestamp. The server sets the created at value using the timestamp GraphQL schema directive.
It has a description. This is the sample for examples in Neo4j and Apache Spark. And then for our selection sets, let's bring back book, title, ISBN, price. Right, just for the fields that we specify. And then we also have this info object, which we'll use to tell us the number of nodes that were created, if any.
Okay great, so let's run this. And says, yeah, we have now a book called graph algorithms. We created one node. If we scroll down, in our console we can see the generated Cypher query, which is neat. So it's doing a create statement, setting some property values. Neat, now we could open this up in Neo4j browser to verify that we actually did create some data. Actually, let's go ahead and do that. If I go back to Aura, I can go to open with and I can either open Neo4j browser, which we saw before, or Neo4j Bloom, Bloom is like a no code visual exploration tool, so Bloom is nice if I want to visually explore graph data without writing any Cypher. But let's do browser for now. And then I will need... We'll close this so it starts with a helpful guide. We'll close that. And then I need to put in my password, which I saved over here somewhere. Great. So I'm in and I can write a Cypher query that says, match on everything, return everything. So this says, find all nodes. Previously, we had like a colon article or something in here that was the label. This a refers to the variable that we can use to refer to any nodes that we match on this pattern later on. So this just says, find all nodes in the database and return them. And it says, yep, you have one book, you're the properties of the book. So that's what we created here. So we've created a single node, that's fine, but we have a much more rich data model to work with. So let's look at creating a review. So let's run another mutation operation. So we'll say mutation. And this is going to be create reviews, which is going to take the input object. Oh, my container has hibernated. Let's restart it. Restarting the browser, do I need to restart this browser? No. Okay. So I just need to restart our container. Yeah. So we're writing this create reviews input. So review, takes a rating. Let's give this book five stars. This is really the best overview of graph data science. Really is. It's a great book. So that's the review, which will create the review node. But we need to connect this review node to the book, no that we created earlier. So we have a book field. And we have two options here. We can do a create operation. So we can create a new book or we can connect to an existing book. So this is a review for the graph algorithms book. So we're going to connect to an existing book. And then we have a where argument that we can use to specify some filtering. So we should probably use the ISBN here since that's like the canonical ID that refers to the book but I can't remember what I typed. I just made something up. So let's just use title, but no it's interesting all of the options that we have here for filtering. We can basically do any filtering on any of these fields of our book type. But what we want to do is filter for where the title is graph algorithms. So this is going to create a review node, set the text and the rating, and then create a relationship to the existing book. And then in our selection set, we'll bring back any reviews that we created. How about text, rating, and then also we want to make sure that it goes for the right book. And then we also have this info object, which we can use to return the number of relationships created. So we'll run this. We get back some properties from our review. It says, yep, we're connected to the graph algorithms book. If we take a look now in Neo4j browser, so we can either write a new cipher query, we just double-click on this to traverse out. Yep, here's now a review node that is connected to the graph algorithms book. And it has a rating text and a created at timestamp. And remember we didn't have to specify that created at value in our GraphQL mutation. This was set by the server because we use that timestamp GraphQL schema directive. That value is set for us.
16. Nested Mutation Operations
We can create complex subgraphs using nested create connect operations in GraphQL mutations. This allows us to create multiple nodes and relationships in a single operation. For example, we can create a new customer, connect reviews to the customer, create orders for the customer, and add books to the orders. The relationships between the nodes are defined in the GraphQL schema, and we can visualize the created structure in the Neo4j browser.
Okay, cool. We can also create more complex structures. So these mutations have these nested create connect operations. So we can actually create more complex sub graphs. Let's try running this one. So instead of typing this one out, I'm just going to copy this. What am I missing? Oh, I'm missing the selection set. And then this piece. There we go. So this is actually a fairly complex operation. So what are we doing? So now we're starting from create customers. So we're creating a new customer, and then for the reviews for this customer, we're going to connect. We're gonna create a new customer node and connect relationships to reviews this customer has written. In this case, where the text says best overview of graph data science, which should match, this node. Again, we should be using an ID or something for the review, but this works. Then we're going to create orders for the customer, and we're going to add the graph algorithms book to that order for the ship to, which this is the relationship to the address. So if we look at our type definitions for order, we can see that order has a books, has a customer, and it has a shipped to relationship field, which is connected to a single address. And the address node has an address and a location, which is a point, which we're specifying here. And the input for creating our address node, which will then be connected to the order, which is connected to the customer. So you can see all of this structure that we're creating in a single GraphQL operation. So let's run this. And again, we could look at the generated Cypher query here. If we wanted to. It ends up being fairly complex again, because we're creating several nodes and relationships. And we can find this in the browser. So now in U of J browser, we can traverse out. See, yep. Here's this user who wrote a review of this book. They also placed this order. Note that the order has a auto-generated order ID. This is because we use the ID schema directive to say, hey, we want to auto-generate this. And then we have the place that timestamp, which is also auto-generated for us. And we can see we are shipping to this address. And the order contains this book. Cool. So that is this concept of nested mutation operations where we can actually create lots of things in a single GraphQL mutation. We can create nodes, we can create relationships, and so on.
17. Querying Data with Neo4j GraphQL
To query the data we've created, we can use the generated query fields, such as 'books' and 'orders'. The Neo4j GraphQL library automatically adds features like sorting, pagination, and filtering to our API. We can sort by fields in ascending or descending order, and paginate using offset or cursor-based methods. Filtering is also available, with different operators for different field types. For example, we can search for books with a price less than $20. The 'where' argument can be used at the root query field or nested within the selection set. It's important to note that the filter is applied at the level where the 'where' argument is used. Let's now take a closer look at these querying features and explore how we can retrieve the data we need from our GraphQL API.
Okay, great. So if you were following along with that, we're going to clear out our database. There's a mutation here in this slide, and I'll paste this. Actually let me do this now. What we want to do is delete all the data in our database. So if you didn't run any of these mutations, you don't need to worry about deleting the data, but then we're going to run this mutation. which I don't have nitro on Discord so I can't paste, but I think I can send this as a snippet. Well I will do this. I guess I'll do this in two. Let me send one for the selection set. Ah, there we go. Okay, so if you have the slides open, this is slide 50 which is this mutation operation. I also pasted it in Discord, but what we wanna do first is open up Neo4j browser. So, in the Aura console, you should see your Neo4j instance listed here. Go to open with Neo4j browser, it'll be prompted for your password here, and it looks like this. And then we're going to run this cypher query, so match, oh, it's in Discord too, match a detach delete a. Make sure you're in the right Neo4j instance, because this will delete all data in the database. And once you do that, then we want to run this big long mutation, which is going to create some sample data for us to work through the next section. So I'm going to go to Neo4j Browser, let's say match a, detach delete. The reason I say detach, so this is matching on all nodes. And then I could say delete a, and that would delete the nodes, but I can't have relationships with the nodes that are being deleted. Relationships without the node, so the detach is just acknowledging that I also want to delete all of the relationships connected to all the nodes that I'm deleting as well. Okay, so I've deleted five nodes. So now I don't have any data in my database, and I'm going to copy this mutation, and go into GraphQL playground here, paste this and run it. And we've created three books and some customers, we can look at this in the database, this is what we created, three books, some orders, some customers, some reviews, some addresses and so on, so basically some sample data that we want to work with, so we'll pause here and give everyone a couple of minutes to clear out their Neo4j instance and then copy and paste this mutation and verify that you have created some data in Neo4j, so this will be the sample data that we'll use to take a look at some of the features that are added for querying with Neo4j GraphQL library and then also we will then take a look next at adding custom logic. Before we get to custom logic the next exercise will be on adding some missing types, so if you're running ahead of us a bit you can take a look at the next exercise if you want to see how we would update the GraphQL type definitions and then we will then begin to add authors and subjects which we did not include in our initial schema but let's pause here for let's to the top of the hour, two minutes if you need to take a break or want to make sure that you've got this mutation working so if you have any errors or have any issues definitely let us know in the chat otherwise we will get going in two minutes. All right. Okay so everyone able to run that mutation to create data in the database? Give a shout in the chat if you weren't if you had any issues with that otherwise we will move on to take a look at how we can query this data that we've now created. Okay let's look at how we query this data. So we looked at the mutations that are generated for us. We saw this concept of these nested mutation operations where we can create nodes, connect to existing nodes, create relationships. Now let's take a look at the query side of this. So we've defined our type definitions. We didn't define explicitly the things like the ordering and pagination and filtering, all these things. We didn't define those ourselves in our type definitions. That was added to the API for us by the Neo4j GraphQL library. So let's take a look at some features of the things that were generated for us that enable us to query this data. So by default, every type that we've defined in our GraphQL type definitions have a pluralized top level query field. So type book becomes books, order becomes orders, just to refer, just indicate that that is a object array field. So each one of these then becomes an entry, a query entry point to our API. One way to think of this is the query field, the entry point to the API, that becomes the starting point for a traversal through the data graph. And of course, as we add more fields to our selection sets, the database query that is generated behind the scenes takes that into account to resolve all the data for us that we've requested. For sorting and pagination, in the options input, or in the options argument, we have input options for sorting by fields of the type in ascending and descending order. We can do offset-based pagination using limits and offset. So give me the first 10, skip 10 for the second page, skip 20 to the next page and so on. And this is where the count queries. So we saw there's a books entry points, there's also a books count, which can tell us the total number of books. So we can use that to calculate our slices for offset-based pagination. So we know how many total books there are, we know we're doing them in slices of 10, so we know how many total pages we're gonna have. We can also do cursor-based pagination with the Neo4j GraphQL library using relay connection types. So here, for example, we are searching for a specific order, and then there's a books relationship field. There's also a books connection field that's available. The books connection will give us the relay spec connection types, which give us things like page info. And also for example, to get a cursor to allow us to do cursor-based pagination for iterating through these connections. We then use this edges and node relay style syntax for grabbing the actual nodes in this case, for grabbing the actual books that are connected to this order. So this query is saying, find a specific order and then use cursor-based pagination to paginate through all of the books in the order. We don't know if we have an order that has a lot of books, this may be useful. So different options for pagination. For filtering, there's a where argument that is included. This is both at the query field and also nested within the selection set. We can also apply this filtering. The filters that are available for each field will depend on the type of the field. So for string fields, we have a common string comparison operators. So things like starts with, contains, ends with this sort of thing for numeric fields. You have greater than, less than, those sorts of things. So here we're searching for all books that have a price less than 20, I guess this would be $20. So I mentioned that the where arguments can be used at the root query field or also nested in the selection set. It's important to note that the filter is applied at the level where the where argument is used. So for example, in this example, we're searching for books where the price is less than 20, and then for those books, the price is less than 20. We're grabbing all reviews that were created after a certain date.
18. Filtering Based on Geo-Distance and Relationships
We can filter based on geo-distance, searching for addresses within a specific radius. The filter is applied at the level where we use the filter argument. To apply a filter at a higher level, we can use filtering through relationships. By nesting the filter in the where argument, we can find orders with a ship-to location within a certain distance from a specific point.
So we're filtering the books and then we're filtering the reviews. We're not sort of saying only show me books that have a review created after this date. This filter for the date is being applied to the reviews of the already filtered books, if that makes sense. So the filter is applied at the level in the selection set where I use it. We can filter based on geo-distance. So on our address nodes, we have a location property that is a point. So latitude and longitude point. For any point fields, there's a filter generated that allows us to search by radius distance within points. So here, we're saying, find all addresses that are within one kilometer of this point. This is somewhere in the area in California. And we get an address in San Mateo, which is the only one that's within a kilometer of wherever this point is. So we just said, on the previous slide, that the filters can filter based on geo-distance. It can be nested, but they're applied at the level where we use the filter argument where we use the where argument essentially. But what if we want the filter to be applied sort of higher up. So in this case, we want to filter orders. So we want the filter to be applied at the root level. But what we want to filter on, is orders that have an address node where the location of the address node is within one kilometer of this point. So to do that, we use filtering through our relationships. So we create kind of a nested structure in our where argument. So here we're saying, find orders where the ship to location distance is less than one kilometer from this point. So you can see we are nesting the ship to filter in this where argument. And we end up with only one order that was sent to an address within one kilometer of this point.
19. Updating GraphQL Schema and Running Mutations
Let's update our GraphQL schema by adding the author and subject types. We'll then run GraphQL mutations to add authors and subjects based on the provided table. If you need help, the solutions are available in the readme file of another code sandbox. Let's take a five-minute break and reconvene after to review the solutions.
Okay, great. So that is a look at some of the generated query semantics that are generated as part of the Neo4j GraphQL library. What we want to do next is a hands-on exercise to update our GraphQL schema. So we left a few things out. We need to update the schema.graphql files. We need to update our type definitions to include author type and to include subject type. And then once we've added those types in the schema, so also think of the relationships that we need to add, then we want to run some GraphQL mutations that are going to add the authors and the subjects to our database based on the table below. So we have three books. We have the authors here of the books and we have the subjects for each book as well. So let's pause maybe five minutes or so, let's pick up again at 16 after the hour. So in the code sandbox that we have open, update schema.graphql file to add the author and subject type. Also think of the relationship directives that you'll want to use to connect author and subject to books and then see if you can write the GraphQL mutations to add authors and subjects based on the table here to the graph. If you get stuck the solutions are linked in the readme file of this other code sandbox. But let's pause for five minutes and then we'll come back and take a look at the solutions. If you get stuck, if you have any issues or questions, feel free to ask in the chat.
20. Adding Custom Logic with Neo4j GraphQL
In this section, we added the author and subject types to our schema. We defined the fields for each type and established the relationships between authors, books, and subjects. We also created mutations to connect authors to existing books based on their titles. The schema now includes additional custom logic using Cypher directives and a new weather type. We've implemented a resolver in the Index.JS file. To continue with the workshop, we cleared our database and set up a new code sandbox. The updated schema includes authors, subjects, and Cypher directive fields. We ran a mutation to create data based on the full schema, including authors, books, customers, and orders. Now, let's dive into the topic of custom logic and explore the capabilities provided by the Neo4j GraphQL library.
Okay. Okay. So that was five minutes, I think. Did anyone get through this? Was anyone able to update the GraphQL type definitions to include author and subject and then craft the mutations to add those to the Graph? Anyone able to get that done? Okay, well let's take a look at the solution. So we have got about 40 minutes left and we have a section on custom logic in the authorization section, which I want to make sure we cover. So maybe let's take a look at the solutions for this fairly quickly and then move on here. So my container has been hibernated, I'll refresh that. So this happens sometimes with Code Sandbox that it will go into hibernation, we need to give it a restart. I think overall Code Sandbox has been pretty good for these sorts of workshops. I think it's... there's a trade off right between these using online tools like this or getting your local development environment set up, which can sometimes be a challenge in these sort of online workshops but I like Code Sandbox, so okay we said we need two types, so we're gonna add an author. Author has I think just like a name and subject, which also probably just has a name, we'll make these fields required or non-nullable, that's what this exclamation point means, that to create an author node, it needs to have a name, to create a subject node, it needs to have a name and then we need a relationship field, so an author, an author can have written one or more books, so we'll make that a object array field and then we wanna use our relationship directive, so the author, let's say author wrote a book, and then the direction, so we're going from the author to the book, author, wrote, book, so in this case, from the author, the direction is going to be out, and then similar for subject, subject will also have, call that field books, And the relationship type, which we call this one, a book is about a subject, and so in this case, we're gonna go from the book node, the book will be about a subject, so for the subject type, the direction will be in. And then we also, on the book type, we wanna be able to go from the book to the author and the book to the subject, so we'll say authors, I think, so a book can have multiple authors, so this I think should also be an array field and we'll need a relationship type, which we said was wrote, so from the point of view of the book, the direction is gonna be in because we're gonna go from the author in to the book. And then similar for subject, so a book can have zero or more subjects really and a relationship directive, so the relationship we said for this one was about, oops, missing our double quote there, so direction this case is going to be going out, so we're saying the book is about subjects, Andres says he's dropping. Yeah, thanks for joining, yeah, there is a recording I think that will be sent out and yeah, thanks for joining, okay, so we'll save this, I didn't command us, we can also go to up here file save, you can see this triggers a restart of our GraphQL API application, and I think we need to refresh this browser to get GraphQL playground to reload the type deaths, but now if we look in the docs, we should have authors, yeah, we have authors and subjects cool.
Okay, so we have this table, we need to write GraphQL mutations to add the authors to our books. So let's do that, so we have a book called Inspired that was written by Marty Kagan. So let's take a look at how to do that. So we're gonna delete that, this is gonna be, I know it's gonna be a mutation. And we need to decide what way we want to start. There are a couple of ways we could approach this. We could do update books and search for the book and then connect, or do a create operation rather from the book to create a new author, or we could do create authors. Maybe let's start with that. So we'll say Create Authors, author has a name, but Marty Kagan and then Marty Kagan wrote a book. We already have the database. So this is gonna be a connect not a create. So we're gonna connect where the node. So you may notice that we have to say, connect where the node if we had properties on the relationship. So we haven't talked about this yet, but so far we've been storing properties on the nodes. If we had properties on relationships, we would have another option here to filter based on the edge object, which is basically, which is anyway, that's why we have the node here, it seems like an extra layer, but it's simply because we don't have the edge filter cause we don't have any relationship properties. Anyway, we want to filter where the title is inspired and let's get some more real estate so we can see what this says. Then we'll bring back authors, name, and books in the title of the book. And then we also want the info to tell us if we create any nodes. Okay, let's identify this and look at this. So what are we doing? So we're saying create authors and here's the input object, create an author, set the name to Marty Kagan, then for the books relationship field, we're going to connect to existing books where the title of the book is inspired. So let's run that. And we say we've created Marty Kagan, and Community Inspired we created one node can verify this if we go to Neo4j let's look for author. Here's Marty Kagan, you can double-click to traverse out, you wrote this book inspired, which is all about product management. Cool, okay, great. So that was updating to our schema, adding authors and subjects. I'm gonna skip going through the rest of the authors and the subjects so we can move on and have time to cover the remaining section, but you can imagine how we use those nested mutation operations to create the authors and subjects. You can find the solutions linked in the sandbox too in the readme if you want to check that out. Cool, so let's move on. So we've talked about writing our type definitions that then drive the database, drive the graphQL schema that's generated that gives us the CRUD operations for creating data. What about custom logic? How does that fit in? That's what we're gonna talk about in this section. So I'm gonna do some setup here, you can follow along if you like, but you don't need to. Let's clear out our database. So because we were going through that exercise and we have somewhat different schemas, I'm gonna do another, match a, detach, delete a. So let's delete everything in the database and then we have a new code sandbox. Let's copy the link for this one, drop the link in the chat. Let's do that again. So this is a new code sandbox, which means we'll need to update our.env file again. So I'm just gonna copy that from this code sandbox. I'm gonna click that link that I pasted in the chat and then we wanna fork this code sandbox. So we get one that is private to us and I'm gonna paste my credentials into that.env file and save that. So our API application will restart. So this schema now includes a bunch of other things. It includes the author. Yeah, so author subject. We also have these cypher directive fields now where we've added some custom logic. We even have a new weather type and if we look in Index.JS we see we've actually like implemented a resolver here. So this code sandbox, we're going to talk about in the context of adding custom logic. So you want to follow along with that code sandbox, you can see some updated code, but to finish the set up we need to run this mutation to create some data based on the correct full schema. So that included authors and subjects. So that is running to save some data in our Aura instance. You can see we're creating the authors for each book, the authors for each book, handful of books, and then creating customers and some orders. I guess there's a couple of notations operations here. Okay, cool. So let's talk about custom logic. So we get a lot that is sort of generated for us for free essentially by the Neo4j GraphQL library based on our type definitions.
21. Adding Custom Logic with Cypher and Resolvers
There are two ways to add custom logic in GraphQL. One is using the Cypher GraphQL schema directive, where we add custom logic by adding Cypher statements to the schema. The other approach is implementing custom resolvers, where we can call out to Neo4j or another database. Using the Cypher schema directive allows us to include the custom Cypher statement as a subquery in the generated Cypher statement, avoiding multiple round trips to the data layer. Implementing custom resolvers may result in multiple round trips to the data layer. We can use the Cypher schema directive to compute scalar, node, object, and object array fields. For example, we can calculate the subtotal of an order by traversing the books in the order and summing up their prices. We can also use the Cypher directive to find recommendations for customers based on their orders and similar preferences of other customers. These recommendations can be nested and queried as well.
There are two ways though to add custom logic. So if we have business logic beyond just our basic create read, update, delete functionality. So the two approaches to that, one is the Cypher GraphQL schema directive. So this is where we're adding custom logic by adding Cypher statements to the GraphQL schema or implementing custom resolvers. So we can implement custom resolver functions with any logic, we can call out to Neo4j and call it another database. We can basically do whatever we want in a custom resolver. So there's some trade-offs here. So if we use the Cypher schema directive, the benefit we have here is that even though we're adding a custom Cypher statement, that statement is still included as a sub query in our single generated Cypher statement. So we still are only sending one query to the database. So just one round trip to the database our custom statements are integrated into that one generated query. So we get to take advantage of the performance benefits even though we're adding custom logic. We're not sort of going down that path of multiple round trips to the data layer with this going down the path of the N plus one query problem. If we implement custom resolvers, well, those customers resolvers those are still called in a nested fashion. So we may end up making multiple round trips to our data layer in that case. So something to be aware of there. And of course, implementing a custom resolver it's a bit more work than just attaching a cipher statement in our type definitions. So we saw an example earlier of the cipher schema directive. I think it was looking at a business recommendation like a collaborative filtering for users who reviewed this business. What are other businesses those users are reviewing? If you like this business that might be a good way to find recommendations. So let's look at the different ways that we can use this cipher statement. So, and these snippets all come from the code that's in the code sandbox. So that code sandbox link for this module includes all these examples. So you can run these and see how this works. So here we are adding a field to order, using this extend syntax in GraphQL SDL, which we can add these as fields on the type definition. I like to use this extend functionality because then I can sort of have my basic type definitions in one place and my custom logic somewhere else and add them together. But it's just a syntactic sugar I suppose. So here we're adding a sub-total field to the order type. So we may calculate this by looking at the order, traversing to all the books that are contained in the order and then summing up the price of each book. Of course, this could be a bit more complicated. We may have discounts and things like that to take into account. But this is one way that we can calculate sub-total. So here we're using the cypher directive to compute a scalar field. So we're calculating a float. And again, note the use of the this. This is a variable that's injected to refer to the currently resolved order. And so to the client of our GraphQL API, this just looks like another field, right? It's not obvious that this is actually being computed. Let's look at this one as an example. This is interesting. So in Playground, oh, we've got an error on this. I don't know why I didn't get an error. Oh, maybe I didn't paste in the right credentials in my.env file, perhaps. Is that what happened? Copy. Paste. And we're in this again. There we go. I worked out time. Yeah, so I think I didn't have the right credentials from my database. But, okay, so what do we want to do? We want to look at orders. And for orders, orders have what? An order ID and a subtotal. So let's run this. So here's all of our order IDs and the subtotal. Now, if you look at the generated cipher quarry, so I gotta scroll way down in the terminal output. If I look at the generated cipher query for that, I can see, okay, we're matching on order, we're returning order ID. And then for the subtotal field, here's our cipher query that we attached to the schema where we're traversing to all the books in the order and summing up the price to get sub total. So that's what we mean when we say that the cipher statement that we're attaching with the cipher directive, that that is sort of injected as a sub query into the one generated cipher statement. So we're still making just one request to the database. Okay, so that's using cipher schema directive on a scalar field. We can also do this to node and object fields and object array fields. So here we're adding a recommended field to the customer type. This is an array of books. So we're looking at the orders that this customer has placed. So starting from this, it's the currently resolved customer, traversing to orders this customer has placed to books that are in the order, and then looking at other orders that contain these books that other customers have placed. So this is saying for all the books that I've ordered, what customers are also ordering the same books that I like? So basically finding similar customers, right? Buy the same books, similar preferences. Then in the next statement, the next match line here, we're going from those other customers, what are orders they're placing with other books that I have not ordered? So what customers are ordering books that I'm also ordering? What other books are they ordering? Those might be good recommendations for me. Again, this is another way to express this idea of collaborative filtering where I'm using the preferences of other users in the network to personalize recommendations for a specific user, which we can express in Cypher. So now we have this recommended field on customers. And again, we can query this. I'll probably skip querying this one in the interest of time here. But now we have this recommended field that gives us books. We can select into books and continue on like a nested structure as well.
22. Field Arguments and Default Values
Field arguments in type definitions for Cypher directive fields are passed as Cypher parameters. Default values can be set to handle cases where no value is provided. For example, the limit field argument can have a default value of three, but it can be overridden to return only one recommended book.
Any field arguments that we define in our type definitions for the Cypher directive fields are passed to the Cypher statement as Cypher parameters, which is quite nice. So for example, on our recommended field, we want to control the number of recommended books. We could add a limit field argument here, and then that's available to us in the Cypher statement as a Cypher parameter. We use the dollar sign to indicate that we want to use a Cypher parameter in that name, matches whatever is included in our Cypher, in our GraphQL argument. Sorry, I have some loud noises going on.
Okay, so this is quite nice. It is a good idea, I think, typically to use default values here. So here we're setting a default value for three. That means we'll, this limit Cypher parameter will always have a value, so we don't need to think about how to handle that in the case if we don't have a value that the user specifies. So we could also, you can also make it a required input, but I like to set a default value. That makes it easy for the client, if they don't care, they can just use the default value. But then we don't have to in our Cypher statement to handle the case where no value is provided. Anyway. So here, we're overriding that default value by saying, no, we only want one recommended book.
23. Custom Logic and Resolvers
We can use Cypher directive fields to return objects and map them to types defined in our schema. For example, we can call out to a weather API in Cypher, retrieve weather data, and project it as an object field in our GraphQL API. We can also use Cypher directive fields for top-level queries and mutations, such as implementing fuzzy matching through a full text index. Additionally, we can add custom logic by implementing resolvers, which are generated by the Neo4j GraphQL library. Resolvers allow us to specify data fetching logic and override the generated resolvers. We can also add custom fields that are not stored in the database by implementing resolver functions. The ignore directive is used to indicate that a field should be resolved using a custom resolver instead of fetching data from the database. Finally, we have an exercise on calculating similar books using Cypher. The last module of the workshop focuses on authorization, and there is a specific code sandbox provided for this topic.
Okay, so we've used the Cypher directive for scalar fields, we've used it for returning nodes, we can also use Cypher directive fields to return an object and Cypher, I think we call these maps, but an object, a map, a dictionary. Every way I think about it, key value pairs. So we can use, write a Cypher statement that returns an object, and then map that to a type that we've defined in our schema. So for example, in this case, we have address nodes. It may be helpful for our delivery drivers, for them to be able to see the current weather at these addresses where they're going to deliver things. So here we're using Cypher to call out to a weather API. We're using APOC. APOC is the standard library for Cypher that adds some additional functionality. So one thing we can do with APOC is call out to other APIs. So here it's APOC load JSON, call it to adjacent API to then work with the JSON data that comes back from this weather API. But note that we have the location, latitude, and longitude available to us because this, which is the currently resolved address address has location property. Then we return this object. So temperature, wind speed, wind direction, precipitation and summary that matches this weather type that we've added to our schema. So this is good for this case where we may be calling out to another API to fetch some data and we just define some types that don't necessarily need to exist in the database. So we don't have weather nodes in the database, but that's okay. We're projecting those from a Cypher statement. And again, to the client of the API, of our GraphQL API, there's no indication that this is calling out to another system, that doesn't actually exist in the database, whatever, like they don't even know that it's coming from a database. It's just another object field current weather. So, so far we have been using these Cypher directive fields on types. So we've been adding fields to types. We can also use them for top level queries and mutations. So for fields on the query and mutation types. Why would we do this? Well, one case might be to expose fuzzy matching through like a full text index or some custom query logic. So we can create a full text index in the database with this statement, which I'm going to skip running in the interest of time here. But then we can query the full text index and specifically indicate we want to use fuzzy matching. That's the tilde at the end here. So we're searching for books that have either a title or description that contains graph but always misspelled graph. But we still want to return useful results to the user, so that's what the tilde indicates. This, by the way, this is a query syntax supported by Lucene. Lucene has a full text engine that is the backing for full text indexes in the Neo4j. So that's the Lucene syntax. So any sort of Lucene syntax we could use here. Okay, so that's how we query it in the database though. How do we expose this through our GraphQL API? Well, here we're adding a book search field to the query type. This book search field takes a search string field argument. We're passing that search string into our Cypher statement to search the book index. We're adding the tilde to indicate we want fuzzy matching. So we've now added a full text backed fuzzy matching field for, searching for books. So now if we search for books and we even misspell graph, we'll still get some useful results. So that's one case where we may wanna use Cypher directive for a custom query fields. We can do something similar for mutations. Maybe we have some custom mutation logic that we have. We have some very specific structures we wanna create, or we have some specific way that we want to load the data. We can create a mutation fields where the logic is defined using Cypher as well. Okay, so those are the different ways we can use the Cypher directive. So custom mutation, custom query fields, scaler fields, returning nodes and projecting objects from Cypher statements that don't actually, or may not actually exist in the database. Another way that we can add custom logic is by implementing resolvers. So resolvers are generated for us by the Neo4j GraphQL library. We haven't had to write any resolvers yet. We have all of this, including custom functionality in our GraphQL API, which is pretty neat and super powerful. Usually we are writing resolvers just to specify the data fetching logic. We can override the resolvers that are generated, or if we have data that is not actually in the database, we want to call it some other system, whatever the case may be, we can implement resolver functions. So here we're adding a field to the order type called estimated delivery, which is a date time. So maybe we have like a logistics system or something like that that we're calling out to, that's going to tell us if we have this in stock and when we think we're actually gonna be able to ship it. In our case, we're just calculating a date at random in this function, but imagine doing something more complicated. Here, we're just calculating a date kind of at random. So when we do this though, we need to add the ignore directive to our schema to indicate that we should not just try to fetch this field from the database, that do not include the estimated delivery field in our generated cipher statements, because that is not data in the database, instead actually call this estimated delivery resolver to get the data for this field. And so this becomes then another field available on our order type. Okay, so we have another exercise. I'm gonna skip this one, we'll leave this for you to do on your own time, since we have only about 10 minutes left. But if you look at the code in the code sandbox that is linked, you'll see a similar field on the book type. And the exercise here is, think of other ways in Cypher that we could calculate similar books, try to modify that, see what the results look like based on that. But we'll leave that for a home exercise, I guess. Okay, so we have 10 minutes left for the final module, which is on authorization. There is a code sandbox specific for this one. Let me go ahead and bring that one up. I think we'll probably let's just talk through the examples here. Let me copy this, copy link. So here's the link to this one, which I will drop in the chat. I shared this link earlier.
24. Authorization with Auth Schema Directive
Let's talk through authorization using the auth GraphQL schema directive. We use JSON web tokens to verify claims and match rules defined in our schema. JSON web tokens are signed JSON payloads encoded into strings. We can define rules such as authenticated, based on roles, allow, where, and bind. These rules protect types and fields in our schema and ensure that only authorized users can access certain data or perform specific operations. We can also use authorization functionality with cipher directive fields to access claims in the decoded token and generate recommendations based on user data. There is an exercise to create a new user and add data from their perspective. Thanks for joining!
You also find all of the queries that we're running here as well in this gist.
Okay, so let's talk through authorization. There is another schema directive available in the Neo4j GraphQL library that is super powerful called the auth GraphQL schema directive. Auth allows us to define authorization rules to protect types and fields in our schema. We use JSON web tokens to verify claims and match those two rules that we've defined in our schema. If you're not familiar with JSON web tokens, it's basically a way to cryptographically sign a JSON payload, and then it's encoded into the string. Anyone can decode these. So you can take any JSON web token. There's this online tool JWT IO, paste it in there, you can see the payload. And then there's a separate key that either a secret string or like a public-private key pair that's used to verify that the token was signed. In our case like signed by an application that is verifying those claims, either by using a shared secret or by signing it with the private key. And then we're gonna use the public key to verify that it is a valid token and that the claims or the payload, the data that's JSON object, that those claims are valid. So in this case, we have a sub claim, that's the subscriber, which is like the user. User ID, that's typically what's used there. Then we have roles. So this token is a valid token verifying that the user is BABLABLA7687 and this user has an admin role for this application. There are lots of other things. So we can add basically any sort of JSON key value pairs to the payload of a JWT. There are some conventions such as sub is commonly used for subscriber roles, scope, things like this. Okay, so let's look at some of the rules that we can define using this off schema directive. So the simplest one is authenticated and this simply means that in order to access the field or type the GraphQL request must have a valid JWT. In the request header. So here's an example where we've added is authenticated to subject. So maybe, I don't know. Maybe we have like a public search for our books and then you can view like the title and author but if you wanna do the subject you need to sign into our application, something like that. So we get an error if we request subjects and we are not passing a token but here we've added an authorization header in GraphQL playground with a bearer token. This is the JWT and the Neo4j GraphQL library will validate that the token is valid by using either a shared secret or a private key and then we will get the data that we expect. The next type of rule we can create is based on roles so we saw that token before that had a roles claim and it was an array and included admins. This was an admin user. By the way, we can also specify the operations for each one of these auth rules so we're saying this rule is gonna apply to create update and delete operations on the book type so this rule will not be applied to read so anyone can read but to create, update or delete a book you need to have an admin role. Another type of rule we can define is allow. Allow will compare values in the database to values in the payload of the JSON web token so here we're saying that to have access to an order we're not specifying any operations here so this applies to all operations for the order including reading that the customer username of the order needs to match the sub or the subscriber claim in the JWT So this says users can only read their own orders or create or update their own orders. So here we are authenticated as this user ML from 7474 and we're saying show me all of the orders for this user but now if I remove that filter and I say show me all orders. Well I'm trying to request orders that I don't have access to as I'm gonna get an error. We can combine multiple rules. So this is an or operation. So we're saying only customers have access to their own orders. Or if you're an admin, if you have an admin role and you also have access to orders. So there's a problem sort of with this one where we're authenticated, we're asking for all orders. We still get an error because we're not filtering for orders for the currently authenticated user. So there may be some cases where we don't really want the client application to be responsible for adding these filters to filter for only the currently authenticated user. So that's where the where rule comes in. So where is similar to allow, and it allows us to specify rules, matching values in the database to values in the JSON web token. But the difference to allow is that where will automatically add those predicates to the generated Cypher query so that the client can, it's free to request data, but that predicate is always going to be added. So they're only going to get back data that they have access to. So here we're adding a where rule to the customer type that says only the currently authenticated customer can access the customer type. So you can only access your own customer data. And now we're authenticated as a specific user by passing this authorization header and passing this bearer token. We're asking for all customers, their username and their orders. But if you look at the results, we're only getting back customer information for the currently authenticated user because we've applied that where rule. Bind is another rule that we can create. This will also match data in the database to data in the JWT. And in this case, we are saying that to create or update a review, the author username of the review, so note we are like traversing the graph a little bit in this rule, we're going from the review to the traversing the wrote relationship to the customer, we're saying that the username of that customer who wrote the review in the database, that username needs to match the subscriber claim of the JWT. So only users can create or update their own reviews, which makes sense. And bind means that this will be applied during mutation. So if we try to author a review and we try to create, connect that review node to a user other than the user or the customer that we're currently authenticated as, then we get an error. We can use authorization functionality with cipher directive fields. So we can define is authenticated rules for cipher directive fields, and then also in any cipher directive field, when we've configured authorization, we have access to all of the claims in the decoded token, which is really neat under the auth cipher parameters. There's an auth cipher parameter, so say auth.jbt.sub in this case right here on the first line of our cipher statements to refer to the currently authenticated user. So we can grab their username from the token, look that customer up in the database. And then here we're generating book recommendations for the current user. So again, here we're looking at what books has this user purchased, what are the subjects of those books, what are other books that have those similar subjects. So recommending books based on similar subjects. So this is nice because we can just have a Books for Current User query field, we don't have to like filter in that GraphQL query to specify the currently authenticated user that's grabbed from the claims in the token from this authorization header, and we get back recommended books. Cool, so we're over time, there's one more exercise, which is to create a new user using the Admin Bearer token, then create a JWT for the new user, signed with a secret, and then add some data from the perspective of that user. So I will leave that for a home exercise since we are out of time, the solution I think is included in this gist that I linked, yeah, down at the end here. Cool, so thanks everyone for joining and for sticking through to the end here. Thanks a lot. We have a lot of other interesting things that we can do with the Neo4j GraphQL library that we didn't have time to cover today, like working with unions and interfaces. There's an OGM package as well that allows us to have a programmatic API that uses GraphQL type definitions, which can be kind of interesting.
25. Conclusion and Upcoming Workshop
We briefly discussed relationship properties and provided links to documentation for further exploration. The Graph Academy offers self-paced training for a more in-depth understanding, using the same bookstore API application. We will be conducting another workshop on Tuesday, December 7th, focusing on building full-stack GraphQL applications using Neo4j Aura, Next.js, and Vercel. If you're interested, join us to learn how to deploy a complete application with both backend and frontend components in React.
We talked a bit about relationship properties, but we didn't actually do anything with them, just aggregations and so on. So lots of other interesting things. These are links to the documentation for each one of these topics if you wanna dive in more. Here's this list of resources again, the slides everyone should have, the code is online so you can find that.
One thing I will mention is this Graph Academy training. If you wanna go in more depth, the Graph Academy training is like self-paced training you can do on your own, you get a certificate when you're done. It's similar content to what we covered today, but in a bit more depth, it uses the same bookstore API application.
Cool, well, again, we're over time, so I think we will stop here. I'm doing another workshop through GraphQL Galaxy on Tuesday. If you're interested in that, let's take a look. GraphQL Galaxy, where are the workshops? Where are the workshops? So, this is this one, building GraphQL APIs with the Neo4j GraphQL Library. We've done that one. On, I think it's Tuesday. Yeah, Tuesday, December 7th at 1800 CET, we're going to do a workshop with looking at full stack GraphQL applications using Neo4j Aura, Next.js, and Vercel. Today we focused just on building the backend piece and the database and the API layer. On Tuesday, we're going to see how to use Next.js to build really a full-stack application that includes the backend piece, but also the front end, and then how do we use Vercel and Neo4j Aura to deploy our application? So, we'll do some things in React. So, if you're interested, feel free to join on Tuesday, otherwise that is all I have today.