Podcast Search GraphQL API
Building a GRANDstack Podcast App: Episode 1
This week on the Neo4j stream we're kicking off a new series building a GRANDstack podcast application! In this first episode, we start building the API and focus on podcast search.
Links And Resources#
- Code on Github
- Neo4j YouTube Channel
- Building a GRANDstack Real Estate search application
- Building a travel guide with Gatsby.js, GraphQL, & Neo4j
- Arrows graph diagramming tool
- Podcast Index API
Hey folks welcome to the neo4j stream. My name is: will sorry we missed you. Last week we were out for the thanksgiving holiday in the u.s, but we are picking up again this week uh. So, if you've been following along, we have been doing the full stack. Graphql book club um, we went up through chapter four uh that i think we will step away from. You can find the videos for that, though, if you're interested uh on the ufj youtube channel uh, which i will link here, i won't open it up since we're streaming there um as one of our destinations, but i will drop a link to it in the chat. So you can find all the recordings if you're watching on twitch, you can find all the recordings of our streams on the youtube channel. But anyway, what we want to do today is kick off a new stream series and what we're going to do is start off with building a podcast application with grand stack, so graphql react, apollo and ufj database are the technologies we will be using um if you've. If you've been following along so far, we've done a real estate application. Let's find that here we go uh, so we did a real estate search application using grand stack, including importing some real geospatial data for property listings. You can find the recordings and all the code on my github and then we also did a travel guide with gatsby, which you can find here. This was a fun one. This used our data set of openstreetmap in central park to find points of interest. We pulled in the mapillary api to find crowdsourced images and then we did real-time routing using the graph data science library. So anyway, that was a fun one as well. So we're going to pick up a new project in that that similar vein of sort of starting from scratch and building something and what we're gon na start today is a podcast application. So, a few weeks ago i was complaining about the the podcast application that i was using uh and someone said well. Why don't you just build one yourself? How hard can it be? I thought you know. That'S that's an interesting idea. I wonder how hard it is to build a podcast application uh. So that's what we're going to do starting today. So this will be. I think, a few, a few series to get done. I'M not sure how long it'll take us, but we're going to kick it off today. So what i want to do today is talk a little bit about the data model and you know anytime. We start a new project. I like to think about what does the data model look like and i go to the arrows tool which i'll drop a link to here in the chat. So arrows is a fairly simple graph diagramming tool online that we can use to create our graph data model. So if we think of the sort of business requirements for a podcast application, well, we need to be able to search for podcasts uh. We need to subscribe to podcasts. I need to be able to view lists of episodes for the podcast that i subscribe to. Has those released and then i need to be able to assign episodes to uh to lists. I think right like i want to be able to create playlists uh, for i don't know like for a road trip or when i go hiking or something some way to prioritize them, because if i subscribe to a whole bunch of podcasts, i don't want to just See one feed with you know them ordered by time. I want to be able to assign those to playlists. So following our our sort of standard process for graph data modeling, the first thing we do is think about: okay from those business requirements. What are the entities? Those become the nodes and i can think of a few to start off with so certainly a podcast. It'S going to be one of those entities and so that'll be a node and then podcasts are going to have episodes episode, yeah and we'll say um that an episode what's the right relationship, type to call that an episode um belongs to that, doesn't sound quite right. An episode, um well we'll say, belongs we'll say, let's just say, of episode of podcast. For now i think that's okay for now um and then we have users as well. So we're going to have multiple users in our podcast application and those users are going to subscribe to so we'll say: user subscribes to a podcast. That podcast has episodes. Maybe has is a better way of saying that so podcast has episode. Podcast has episode reverse there. We go and then a user is going to create a playlist, we'll say maybe owns so we're saying owns here, because maybe maybe playlist is a thing that we end up being able to share um, we'll think about that down the road. But this. If one user owns it, one user sort of has the permissions to update it. Maybe he can share it to other users later on, but we'll worry about that later and then a playlist contains episodes so we'll connect the playlist and episode and sure use. This has episode so user owns a playlist. Playlist has an episode. Episodes are connected to podcasts and a user subscribes to a podcast. So i think this data model defines the entities pretty well and how they're connected next. We can think about the properties while a user is going to have, let's say a user id. Maybe a screen name, something like that for now i'll, just think of a user having user id and then a podcast is going to have some sort of id. It'S going to have the feed url so podcasts, if you're familiar with how podcasts are published. Podcasts are published on rss feeds, so there's some url that when you go to retrieve that is an xml document and that xml document has pointers to the episode files. So the actual audio files and metadata for each episode so we'll at some point need to figure out how we're going to parse the feed url to figure out what episodes we have. But we'll worry about that later today, we're just thinking about modeling and then we are going to implement search in our graphql api um. It probably has a title, a description and what else maybe artwork and probably categories. Now we probably want to model categories as a node, so something like this - that is a string, something like that. Maybe and the reason we do, that is perhaps we're going to have a podcast recommendation feature where we want to find the podcast that a user subscribes to. We want to find what are similar categories to find other podcasts. This might be a good opportunity for using some of the graph algorithms in the graph data science library for looking at some of the similarity, metrics or perhaps maybe we'll infer a category hierarchy that might be interesting, okay and then an episode uh. Well, an episode is going to have similar information as well right. It will probably have an id for it. We will have the audio file for it, so some url um, where we can fetch the actual audio file. It probably has a title, a description and some show notes, and then a playlist probably has some unique id to refer to it, and maybe it has a name. So this is my road trip playlist. This is my vacuuming playlist, something like that um we might store. Let'S see, we also need the uh, maybe a pub. Let'S call this publish so the date time that the episode was published and then we can also keep track of like what are the most recent podcasts that you subscribe to. So we can keep track on the relationship on the subscribes to relationship when the user subscribed to that, and also perhaps when the user added this episode to the playlist okay good. This is a pretty good data model. So far, the the next step in our iterative data modeling process is, can i think, of now a traversal through the graph here that answers uh questions that address the business use case that i have so, first of all, as a user, i want to be able To view lists of podcasts that i subscribe to sure i'll just look up my username by id traverse along out the subscribes to relationship find all the podcasts that i subscribe to and grab the title description artwork and render that in a list of my application great, I want to look at um all of the episodes, so a feed in my podcast application. I want to show the user for all of the podcasts that you subscribe to here are all the episodes um ordered by the most recent episodes first well. Similarly, i look up the user by id traverse to the podcast that they subscribe to and then for every one of those podcast nodes traverse out along. This has episode to find all the episodes order by published to show the most recent first uh and so on. I can do the same for my playlist, find all my playlists find all the episodes in each playlist cool. So that's a good start for our data model. I like to think of data modeling as a very iterative process. So as we go along, we may tweak this data model a bit if it doesn't suit our needs, but for a cursory start i think we're off to a good start here. So the first thing that our podcast application needs is search right. So i need to be able to search for podcasts before i'm going to do anything else right. I guess i guess we could force users to enter a podcast by url, but that's not uh. That'S not going to be a very user-friendly way to go about that. So there are a few ways to approach this um. We need a podcast index, so we need some list of all of the available podcasts in order to build search - and there are a few indexes out there. I think the the oldest more common one is the apple itunes index for podcasts, which i think is is available uh. You can sign up for a developer account and and access it. I believe, um. What we're going to do, however, is use. A newer index called the podcast index, so i'll drop a link to that in the chat, so the podcast index. I heard about this. Actually, i think on another podcast. I can't remember where exactly i heard about it, but basically podcast index aims to well be an api for an index of podcasts and they believe that you know it's important to keep podcasting decentralized. One of the the really neat things about podcasts is that anyone can publish a podcast simply by publishing your rss feed and then indexes pick up that rss feed and aggregate your episodes and make them available in their apis. So you can just create an rss feed. Submit it to apple index and then it gets picked up on other indexes like this podcast index um. So i like that. I like that mission. That sounds good. This is what we will use. Let'S take a look at the documentation, so the podcast index api uh tells us how to authenticate a request. Okay, so we're gon na need a uh, a key and an api key and an api secret. Okay cool. So we'll need to sign up and figure out how to get that. But let's look at the endpoints here, so we can search for podcasts uh by a search term: okay, cool. We can search by feed, url uh and by id and itunes id id okay cool and then once we have a podcast feed and we have the id or the feed url of the podcast. We can get the episodes okay great and then we can get the most recent episodes and feeds as well, and then we can also. If we want to publish to this index, we also have write operations for adding as well and then here's the response structure for a podcast. Let'S look at a live example. I think there's a live example here. So here's one searching for by searching for feeds by term and the term we're searching for is batman university make this a bit bigger. So we get back a json object, cool and we have account the query that we searched for and then description. I guess if we didn't find any this would say no matching feeds found or something like that. Okay, so cool, we have title for the podcast, it has an id, so i'm guessing this id maps to something specific to the podcast index id. But then we also have uh the itunes id, which is more of a global canonical, since the itunes index has become kind of the default, i think for podcast search, okay and then we have description the url. So this is the the feed url. If we go here, we should see like an rss feed yeah there we go so we see some xml so we'll have to figure out how to parse that xml once we start pulling in feeds, but we'll not worry about that today and then we have artwork. So images for the podcast - okay, cool, okay, great, so that looks like the endpoint that we want. So in our application. We want to be able to search for podcasts by some search terms. So i open up my application and start searching for, i don't know like neo4j graph related podcast news, uh npr podcast, whatever you're you're interested in at the time. Okay, so that looks good um. We also need to sign up for an account so that we can get an api key, which i have done already. There'S my keys. Don'T worry, that's just just part of the key doesn't include the secret, so i'll make sure not to flash those on the screen. Question from roman, how can you implement an eav model in the grand stack when the user decides what properties the entity will have? What is eav is that a common thing i should know entity attribute value model to encode in a space-efficient manner, entities where the number of attributes can be used to describe them is potentially vast, but the number will actually apply to a given. Interleave is relatively modest. Also similar to a sparse, matrix uh, so is the idea here that you're sort of defining the universe of possible attributes, but many entities will only have a subset of those attributes. I guess the the real question here is: how does eav map to the graphql type system since in grand stack, our api layer is graphql and graphql has a strict type system where we need to define what fields exist on what types, how those types are connected And and so on, we can, we can make types types can have nullable and non-nullable fields. So perhaps you define the universe of attributes as nullable fields on a type. That'S not great, because you'll end up with a whole a whole bunch of types or a whole. Bunch of fields on a type we did something uh kind of similar to this in the travel guide project that we did. If we look at the schema where's my schema, so in this one we had points of interest and points of interest. So this data comes from openstreetmap and we had tags where's. My here we go, we had tags and tags can be arbitrary, key value pairs that a user defines when they upload the data to open street maps. Openstreetmap is crowdsourced data, and this can be something like amenity is the key and like restaurant is the value it can be. Something like address is the key and then the address as the value it can. If it's a business, it could be something like um. The opening hours or something as the key, so we need a way to model that sort of arbitraryness of the key value in graphql. So the way we modeled, that was, we added a tag, type that had a key field and a value field, and this allowed us to store arbitrary key value tags in the database exposed to the graphql api, but still adhere to the graphql type system. So something like that sounds somewhat similar to the entity attribute value value model, um that you mentioned. I guess this is this seems kind of similar uh, perhaps an alternative to sort of how we think of the property graph model, but yeah good question. I guess the ultimately it comes down to how how would you model that in graphql adhering to the graphql type system? Okay, so back to our podcast index api um, we want to figure out how to use this endpoint to search for podcasts. Let'S go to neo4j desktop, so i'm going to start a new project in neo4j desktop. Let'S call this grandcast fm. That'S we're going to call our podcast application will be grandcast i'll start. A local database, 4.21 latest grandcast set an initial password. We'Ll need to remember that later, so what i'm going to do is create a new database and right away, i'm going to go into plugins and install apoc. So what i want to do is actually use neo4j and cipher to query this api, so i'm actually going to have the database take care of querying that api and returning results. Um that may sound a bit a bit odd, but um we'll we'll see how we can do this using some of the functionality in cypher, so apoc, this library that i i just installed. This is a really the the standard library for neo4j that has a whole bunch of procedures and functions that give us additional capabilities. One of those functions or procedures, rather is apoc load json, which allows us to make network requests to json. Endpoints like like this one and then work with the results in cypher. So what we're going to do is figure out how to query this podcast api using cipher and apoc, and then we're going to do is move that logic into our graphql layer. So we're going to figure out the cipher query to query this podcast index api, then we're going to create a graphql api that will then expose that to our client application, a question from the chat. How do you decide if you go for a relational database versus graph database for a project? In the end, all data is more or less related, so does it make sense to always choose a graph db for a new project? Yeah, that's a good question. Um! There'S a few things to think about. I guess like i could. I could talk about this for a long time, but i'll i'll try to give you the the shorter answer. I think if you talk to people who um who have used graph databases in their projects, oftentimes you'll hear about a few use cases where graph databases really shine because they are optimized for different workloads. So, for example, things like a real-time recommendation, query or a real-time routing query where you, you need to be able to traverse a graph very very quickly, and you know you have like tens of milliseconds, because this query sits on the hotpath for loading a page in An application or you need to figure out the logistics route before the package comes off the conveyor belt, or something like that where performance for these sort of traversals is is really key. If you have some use case like that, yep totally makes sense to use a graph database, something like fraud detection, where you're dealing with uh in a financial services account financial services, institution accounts and users and transactions that occur at merchants. And you have lots of this connected data and you need to be able to model the complexity of that and also maybe leverage some of the tooling like visualization graph algorithms that can help you analyze. That yep totally makes sense for using a graph database. I'M going to go for a graph database in those projects and then you have other kinds of projects like, for example, um. You might call them like crud apps, where you know i'm building a podcast application or i'm building um. I don't know like a blog, maybe like a medium type publication, something like that, where it's not clear, necessarily that there's the ability to take advantage of the complex query. Nested queries, i'm going to deal with a graph database and another database might be acceptable performance wise. I think another aspect to consider, though, is the data modeling like? How can you ex? How can you express the complexity of the application data? So if we look at like our podcast application, we could model this in a relational database. As you know, an er diagram and have some join tables - um, that's! Okay! That'S doable um! It'S a lot more natural, though i think to express your data model as a graph. I think you know we think in terms of graphs how entities are connected and so on. So, as our data model gets more and more complex, you don't have that impedance mismatch of how you think about the data versus how you store the data versus how you use the data in your application and then, when we add how we want to expose this Data to the client - and we we start talking about using a graphql api that then introduces a whole another layer of zoom into this diagram here, a whole other layer where the data model that we're working with in the database then is the same data model that We'Re then exposing as a graph to our client application. So again we don't have this mapping and translation layer of going from like a relational database to a graph model for our graphql api. We just sort of have one schema to work with, for our api application and for the database so anyways. It also comes down to to what sort of tooling exists for your ecosystem as well. I think that's that's a really important consideration right, so so with grand stack. I know that there's there's good support for working with graphql and neo4j in the node.js ecosystem, so i know that those sorts of integrations for that that sort of full stack framework is there. I know i can deploy that on different services, so that's another consideration as well anyway. I could talk about that um, more nuanced aspects of that for a while, but uh, but i'll leave it at that. Hopefully that at least gives you some some things to think about. Okay. So what i want to do is use this apoc load json procedure to query my podcast index endpoint and search for some podcasts. So the first thing we need to do: let's open up, neo4j browser um, let's make sure, make sure we don't have any data in here. I did start a new one right, yep, so empty database and then, let's make sure that we installed apoc yep we've got apoc procedures, cool so uh. If we look at this, we can actually, let's look at an example, import from stack overflow api. So we start off with the url, so this is very similar to uh the podcast index api that we're about to query. Let me make this a bit bigger, where's it. Here we go where we have some query parameters for like what we're actually searching for this. One has a bunch of things in there for searching for questions on stack overflow and we say, call apoc load json. We get back this value so this this is now a json object and then we can iterate over that using cipher, okay cool. So that's kind of what we want to do. If we look at we look at this endpoint. It says oops says hit this end point. So what we're gon na do is uh call apoc load, json, yield value, return value so we'll just use their example. I spelled yield wrong and it says that server turned 401, so we are not authorized to make that request without sending an authorization header. So we use an amazon style, request, authorization, token, okay, and we have to set a bunch of headers okay. So we need to set a user agent header. So what application are we sending this from? We need the current time: okay, cool. We need the api key, okay and then an authorization token, which is a sha-1 hash of the auth key, the corresponding secret okay and the current date time encoded as a hexadecimal value, okay cool. So we need to add some headers to our request. So fortunately, this procedure, apoc load, json params, will allow us to add headers to our request, so we can put our authorization in there so, like i said, i signed up for a free developer account and i got a api key and an api secret so that We can make authenticated requests. Anyone could do that. Just go to podcastindex.org and put in your email address and you'll get free access as an open api. They just have tokens, so they can. I don't know revoke you if you abuse it or something i i'm not. Even sure, if there's um aggressive rate limiting or anything like that anyway, so you can sign up for api keys, i already did that and what we're going to do now is change this to apoc load, json, oops, apoc, load, json params, so we're going to pass The url and then we're going to also pass our headers and then i think we also need some other arguments, but we'll leave that out for now. So, okay, so now we want to do. Is it needed what a user agent header? So this is going to be grand stack. We need auth date was it so this is going to be some timestamp, so we have a timestamp function. Um. I think we need to convert that to a string we'll fill with that in a second uh. Then what else was that there was x, auth key next auth key? This is my url key, so some secret uh x, no, it's not x off the next. One is just authorization right, yep authorization, uh and this one. This is some like token that we have to concatenate a bunch of things and and sha one hash it okay, so the first thing we need to do is uh figure out. How do i get my secrets in here? Well, apoc has, let's find it here. Apoc has static values that we can store is that in text, maybe no cypher execution, nlp background operations operational, let's search for it. Apoc static, static value, storage in the miscellaneous uh category, uh miscellaneous static values, yep there we go okay, so i can store values in the neo4j configuration file and then reference those with apoc static get. So if i go here, it's it's not this file there's actually actually reads from apoc.conf file. So if i let's see open terminal, this is going to open a terminal to this uh installation of neo4j. So this instance, so then in the conf directory. What i want to do here is create a apoc.com file where i'm storing. So it's going to it's going to be something that looks like this, like apoc doc, static dot. Let'S call it podcast key and apoc dot static, dot, podcast secret. So this allows me to define secrets without having to write them. In my cipher query, i can just store them in my com. My configuration file uh, let me go back to where did that terminal go so i'm gon na do this off screen here, because i don't want to type my credentials in the recording here. So i'm going to create two values in an apoc.com file: apoc static, podcast key epoch, static, podcast, secret, um, okay, so those are in there now. So now i can reference those by saying: apoc static, podcast, key okay, so let's figure out this time stamp. So the time stamp, it says epic time as a string. Okay, so unit epic's time, which is milliseconds um as an integer, does not include a decimal point. Okay, so i want this in seconds. So i have this there's this timestamp function that gives us milliseconds, but i want seconds so, let's, let's move this up here, because we're also going to need to reuse this value when i concatenate things in the authorization token. So i'm going to convert this to a string and it's going to be the time the result of the timestamp function, which gives me milliseconds, divided by a thousand, to give me seconds as timestamp. So that's going to be a string here. Podcast key we're reading from apoc and then the authorization token it says is the sha1 hash of the key, the secret and the date value concatenated as a string. Okay. So fortunately, if we go back to our apoc docs under text functions - and i think this is at the bottom yeah here - we go hashing functions so apoc to the rescue again has a apoc.util.sha function sweet, so we can say apoc, util, sha, sha1 and apox static. Oh right, this is not it's not apoc.static.podcast key, it's about.static.get and i need to specify what value i want to get from the static value store. So when i get the podcast key and this is going to be apoc static, get uh. Let'S look at the key. First concatenated with the secret concatenated with the timestamp: okay: let's try that expression in with must be aliased, oh yeah, with this as headers. So this object that i'm creating here is going to be alias to the headers variable and then i'm going to pass that in my call to apoc load, json params okay procedure call does not have the number of arguments yeah. So there's there's, like some other configuration things here, what a payload, which i don't need to send a payload and then some configuration, i don't need so yeah, we'll just put empty strings for those two arguments: okay and we still get a 401. Oh, i think do i need to. I think i need to restart the database after changing the configuration so that those configuration changes get picked up. Roman in the chat says, am i neo4j for implementing access service need to be able to inherit permissions across your organization structure yeah. I did any access management. That'S a great graph use case when i did it in a relational database, tried to document couldn't think of anything better than drawing a graph yeah. That'S another going back to talking about sort of like the core, uh use cases and reasons for using a graph database. When you start thinking about those complex identity, access management rules, a graph is a great way to represent that, and that is a very non-trivial problem to model and reason about for sure. Okay, we restarted the database. Let'S try this again. There we go so the reason we had to restart the database is because i added those values to the apoc.com configuration file and we needed a restart to pick that up from the store from the static store cool. So we searched for the batman university podcast. That'S just the example that podcast index had in their docs um. I don't i'm not familiar with that one. Let'S change the search term to neo4j and we should get back the yeah. We get back. Rick'S, graphistania, podcast cool okay. So we know how to write this. In cipher um, what we want to do now, though, is uh, create a graphql api to expose this to our client application, and to do that. We'Re going to use grand stack, of course, my favorite full stack framework uh and what we're going to do to get started really quickly is use. This create grand stack, app uh, create grand stack, app cli, which is nice, because i don't really have to install anything. Other than to have node installed, so we're going to say, npx create grand stack app. So this is going to skeleton out a new grand stack application and let's call this grand cast fm. So this is going to download the latest release of the grand stack starter project and then ask us a few questions to configure the application. Okay, it wants to know which front end. Do we want? Let'S, let's come back to this? Let'S do api only for now, since we're just working with the creating the api. Yes, we want dependencies. No, we don't want to get repo. Yes, i have a database, it's the default local host. No, i don't need encryption default user and the password is. Let me in which is the password i use for everything uh okay, so this is now installing dependencies uh removing any front-end templates. So you saw there. There are a few options now for the front end in a grand stack, app, there's react, react, type script and then some folks from the community have added angular flutter, oh, and there is a view on now that adam implemented. We just need to add that to the cli okay, so we installed our dependencies and now it's ready to go so we're gon na cd into grand cast fm and let's just go to the api directory and open that in vs code and we'll fiddle with our Window sizes here, hopefully we can see enough of that okay. So if you're familiar with grand stack applications, we have a graphql schema the the default. Let'S look at the grand sac starter project readme, so the the grandstack starter project uh starts off with a business reviews. Application um: let's look in react yeah. I can see some screenshots sort of anyway. It'S a business reviews, application that has scripts also for seeding the database. We can also see the database. I closed the cli, but it had the command for how we we want to. We can see the database with some fake data um according to this business review schema. So it gives us a schema for like users that are reviewed a business and then there's a dashboard ui and then like a business search ui. That shows how to use graphql with the different front ends. I'M going to start with a blank schema, so we'll just delete that. So all i want to do today, though, is expose this podcast index search in my graphql api. So i don't. I don't actually even want to store data in the database right now, i'm just using uh the graphql api and cipher uh as a way to expose that api for searching for podcasts. So i want a query type and make this a bit bigger. There we go and we'll have podcast search which is going to give us. Let'S call it podcast search results and then our podcast search search result singular, and then the brackets indicate this is an array type. So podcast search result is going to have what, like that itunes id. Let'S call this, i guess it doesn't matter actually, since we're not storing this in the database anyway, um a podcast search result is going to have an itunes id a title, a description, a feed, url artwork and so on. Okay, so i'm not fetching data from the database, so if you're familiar with the neo4j graphql integrations, let's, let's back up a little bit and take a look at that uh. Let'S look at the quick start, so the neo4j graphql integration is called near to graphql js and the goal of numerous graphql js is to make it easier to build graphql apis backed by neo4j. So we create graphql type definitions and then those type definitions inform the data model in the database and then we're able to provision a graphql api with data fetching logic. So all of the create read, update, delete operations. Uh are just sort of generated for us automatically from these graphql type definitions. So it's a really really nice way to get a full graphql api up and running without writing any like boilerplate code. So that's really nice. We can also use it, though, to add custom logic using cipher, so we can attach cipher queries to fields in graphql. We can do that for fields like scalar fields on a type we can do it for object and array fields as well. We can also do it for top level query and mutation fields. So what we're going to do now is for this query field. Podcast search the logic for this is actually going to be defined by a cipher query. So we add that cipher schema directive. That says, hey bind the results of this field in this case uh the root level field for for our api. Do some cypher statement. What cipher statement uh this one that we just wrote. So i'm just going to copy paste that so now we'll have this podcast search field anytime. We request that in the graphql api we're going to execute the cipher query, and we need to make sure, though, that the results that we return map to this podcast search result object. So, instead of just returning value what we need to do, if we look at the example this one, so this object here, this is what we're saying is the podcast search result, object and that is embedded in this feeds array, so we actually are going to want To we're going to want to unwind and iterate over that, so we're going to unwind value dot feeds as let's call it feed and then we're going to return itunes returns and return an object with the key itunes id. It'S gon na be feed dot. Itunes, id oops, itunes, not in tunes itunes. Title is feed.title. Description is feed. Dot description, uh feed url is feed.url, since it's called url over here and then what else do we have artwork? Artwork? Is feed.art work, okay, cool, so we're gon na uh we're gon na unwind over this feeds array and return objects grabbing some values out of that feeds array cool. Let'S take a look at index.js, so we see what's going on here, so we're importing those type definitions that we just wrote: we're getting apollo server we're getting a neo4j driver instance we're getting. This make augmented schema function from neo4j graphql js, we're reading some environment variables, which, if we look at that dot env file, these values were these are just the defaults, but they were populated by us populated for us with that create grand stack cli. So when it asked us what is the connection string for the database, do you want an encrypted connection that populated this file? We then create a graphql schema object by calling neutral, graphql js's make augmented schema so make augmented schema. It takes graphql type definitions and then generates resolvers, so resolvers and graphql. Those are the functions that are actually responsible and have the logic for fetching data from the data layer, so those get generated for us. So we don't have to create those. Typically, when you create a graphql executable schema you're, attaching the type definitions and the resolvers, but because those are generated we'd have to write those which is really nice, oftentimes, there's a lot of boilerplate logic that goes into writing those. Then we have this configuration. That is excluding a type we'll leave that for now, but we'll come back back to that in a second, then we create a driver instance that connects to our neofj database reading the values from that env file. We do this initialize database process, which i'm going to comment out, i'm going to skip that for now. We'Ll come back to that, then we create an apollo server instance passing in the driver instance. So the connection to neo4j and passing in the executable schema and then we play any middleware. If we have it, we don't have any middleware to add now and start up our api. So, let's get a terminal and npm run start says: our api is running on localhost 4001 graphql: here's graphql playground. If you look in the docs tab, we can see hey cool, we have some queries and mutations, so this is interesting. We have podcast search. So we go back to our schema, podcast search yeah. That'S that's the query field that we added here but note that our neo4j graphql integration has also added a podcast search, result query field and then create update, delete, merge mutations for that. So there, the default convention is that any types that i declare in my schema uh, that that maps to nodes in the database and then i can define relationships between types and and then that also maps to relationships in the database, but that that data model then Informs the create update, delete operations that are generated in as part of the generate api, but because i've written the logic here for this query field, i want to exclude the podcast search result, type from the generated queries and from the generated mutations. So we'll add those now into the configuration to exclude we'd, also use a directive here to do that, but we have it in the configuration here and that change didn't get picked up. Oh right, i want to do npm run start colon dev, i think, will give us yeah dev mode. That has a watcher and also log our generated queries. So now, if i refresh graphql playgrounds, i'll now only have one query field. So those generated query fields: query mutation fields are no longer there because i excluded those in the configuration. Okay cool. So can i search for podcasts? Yet so, let's say podcast search, podcast search doesn't take any arguments yet. Can i get a title description? Okay, cool? Well, it gave me it found the graphistania neo4j podcast. If i look at the cipher query here. Well, that's because for the search term yeah i just hardcoded neo4j, so okay, we've implemented a podcast search api that only returns the neo4j podcast um. Okay, that's that's a good podcast, but we may want to to find others to listen to. So what we're going to do now is add an argument to this podcast search field. Let'S call this a search term. This will be a required string, so we need to specify a search term with these cipher directive fields. Uh anytime make this a little bigger it's hard to fill that code on the screen, though, maybe let's slide this over a little bit, give a little more space for our code. Here but anytime, i add a field argument to a field. That'S using a cipher schema directive, whatever i've passed in for this argument, value gets passed as a cipher parameter to the cipher query here. So what that means is. I can replace this where the q query term is hard coded. I can then concatenate that with the search term, cipher parameter. So if i refresh that i need to refresh playground for it to know that i need to pass in a search term, and now we can search for well. We can search for the neo4j podcast and make sure that didn't break anything, but now we can also search for i don't know, maybe like npr podcasts. What'S your what's your favorite podcast uh drop the name in the chat and we'll we'll see if we can find it okay and i can add, like the artwork, the feed, url and the itunes id. So i can now get all of this information cool um. What else? Oh, we said, we had categories as well right, conrad says the xml tech podcast. Oh man, atp, that is one of my favorites as well. Uh, accidental tech podcast failed to invoke illegal character. Um, oh right, so so we got an error because the url just took our search term uh exactly including the spaces and tried to hit that url uh. So we need to url and code the input. So i think apoc is going to save us again. Are we on the text page? We are. We don't want. Base64, though, is this one of our text functions? Now we were just looking at the text functions. Is it a miscellaneous utilities text and lookup? No, i know we have a apoc url in code. Oh yeah. It is one of the text functions, apoc text, url and code recur return, the url encoded text. So that's what we want to do to kind of sanitize that user input is called apoc dot text dot url in code. So this is going to encode those special characters like spaces and slashes, and things that we can't include in web urls cool. There we go so now we get the results, so accidental tech podcast, three nerds discussing tech apple programming in loosely related matters. That is a good description of it if you, if you are familiar with atp, it's basically three nerds talking about apple devices for an hour and a half uh, what else mac break weekly? Let'S see if we have that one mac break weekly yep got that one latest apple news cool we can search for you know if we just want to like search for a news. Podcast, we'll find a bunch of things, but i think we also want to return categories uh to help us to help us make some decisions does not have categories. Where are my categories? Oh, this one just doesn't happen to have any. Let'S go back here. Look at our neo4j example yeah. Here we go here's categories, so categories categories is actually an object here: okay, um! I don't want to represent categories as an object. I don't know what the the key is. 102.. I there this might map to like how i don't know itunes does podcast categories or something like that. Let'S, let's treat categories as a string array and what we need to return then is feed dot categories. We want to return those just the values. So if we had a values function in cipher, we could do that. We don't. However, we do have a keys function, though, does that just work on nodes? I think that also that works on any map right, uh yeah. So if we add oh, let me refresh so it picks up that i've added categories as a field, so i can add categories and i get the keys of the categories. That'S not very, very useful. So what i want, though, is to transform feed.categories this this object. I want it to just get the values from that and i think i think we're going to use another apoc function. So if you go to map functions and take a look at apoc.map.values takes a map and the keys that we want to get the values. For so we can say: apoc dot, map, dot, values. What is the the map or the object? We want to get the values from its feed.categories and then we want to get all of them. I don't know if, if that's a default, but we can say keys feed.categories, we just saw how to use that one okay, so this should transform feed.categories from the podcast index api into from a map into just a list of strings. Let'S try that yeah there we go so this pod news, podcasting news news daily business technology if we go to like npr, let's search all the npr podcasts and see here and now, arts 1a is news on point: is arts invisibilia? It doesn't have any categories and so on cool. So here is our podcast search api. So we now have that so our users can search for podcasts um we're not. We haven't actually stored any data in the database, yet so it's kind of interesting. We we've just sort of used cipher to query the podcast index api, and then we created a grandstack graphql api and bound that cipher query. They were using to fetch data from the podcast index using cipher. Now what we need to do, though, is actually store data in the database, so a user searches for a podcast. The next thing they're going to do is subscribe to it. So when a user actually subscribes to a podcast, then that's when we're gon na write data to the database, so we don't want to like replicate uh what the podcast index is doing right like it's. It'S a search index, it's um, keeping track of anytime new podcasts are added to like itunes and making sure that those are updated. We yeah and it's a free api, so we don't want to replicate that functionality. We don't want to build a podcast index. We only just want to keep track of for our users when they've subscribed to a podcast uh, then we'll store it in the database and connect it to that user. So that is what we will pick up uh next time. I think this is a a good spot to end for today uh, since we we created a little bit of functionality here um. I will push this up to github. Let me create the repository now, so i can at least drop the link in the chat. What should we call this grand cast fm public create repo cool, so grand cast fm i'll? Here'S the link i'll drop that in the chat i'll push up the code for this later on, so you can find it there and then you can also find the recording for for this stream and all the others on the neo4j uh youtube channel. If you're not watching on youtube, that's where it is, but you can also find these on my website. So i created a recently a videos section uh on my website, so you can find recordings of uh it's now. It'S mostly just these streams, oh and then a couple of meet up and conference talks. I did um, but anyway i just thought that would be a good place to add some of the some of the videos recordings of the streams and whatnot it's nice. Also that i'm also capturing any any links that might be relevant as well there um cool. Oh, i also recently created a newsletter. It'S just sort of a personal letter newsletter sharing things that i've been been working on. You can read the some of the previous uh issues here so, for example, some some blog posts using uh, the new neo4j charts graph, app and and bloom uh. So, anyway, if you want to keep up with kind of what uh, what i've been working on, you can subscribe to that and you'll get in your inbox, uh blog posts and things that i'm working on anyway. That'S, i think, is a good place uh to end it um we'll pick up next thursday, so we do this stream every thursday at 2 p.m. Pacific and like i said, the next thing that we need to do is figure out now how to store podcasts that users have subscribed to. So i guess the first thing we'll need to do is figure out how to deal with users, so maybe we'll maybe we'll just build a user authentication is probably the next thing right. So user needs to be able to like sign up for our podcast application and create an account and then they need to search for podcasts and then subscribe to a podcast. So we'll try to implement that next time with creating users and then users searching for a podcast and subscribing to a podcast we'll do that next cool. So thanks everyone for joining today, and we will see you hopefully next thursday, at 2pm pacific, alright, cheers bye,
Subscribe To Will's Newsletter
Want to know when the next blog post or video is published? Subscribe now!