Images & Wikipedia Data w/ GraphQL
Building A Travel Guide With Gatsby, Neo4j, & GraphQL: Part 3
We look at 2 different ways to add custom logic in Neo4j GraphQL, adding images of our points of interest from the Mapillary API and then adding data from Wikipedia.
Links And Resources#
- Hi everyone, 01:15 welcome to the Neo4j stream. 01:18 Today we are continuing on with building our travel site 01:26 our travel guide, I guess travel guide site 01:29 with Gatsby, Neo4j and GraphQL. 01:35 All the code is online, I'll drop the link in the chat here. 01:42 Here we go. 01:46 Cool, so this is the third session. 01:49 In the previous session, 01:51 we looked at the different ways that Gatsby uses GraphQL 01:57 as the source for creating content in a Gatsby site. 02:05 And we started pulling in some data from Neo4j, 02:10 so we've been using the Neo4j Sandbox. 02:23 Sandbox, I'm just grabbing the right link here, 02:26 this one so the OpenStreetMap sandbox. 02:29 So you can use this link that I'll drop in the chat 02:33 to create a new OpenStreetMap sandbox in Neo4j. 02:37 So we've been using this data, 02:42 which has data imported from OpenStreetMap of Central Park 02:49 and specifically points of interest in Central Park. 02:52 And we saw how to pull that data from Neo4j 02:59 through a GraphQL API, 03:00 so we've been building the GraphQl API. 03:03 If we take a look at our project 03:04 we have sort of two main pieces here 03:08 one is the Neo4j GraphQL project. 03:11 This is where we are using the neo4j-graphql.js project 03:17 to create a GraphQL API 03:20 that talks to our Neo4j Sandbox instance 03:23 and then we use Gatsby to pull this GraphQL API 03:29 into the GraphQl API that Gatsby uses 03:32 so Gatsby is a static site generator that uses React 03:38 and GraphQL and uses that content from the GraphQL API 03:46 to create pages. 03:50 So let's go ahead and fire up first 03:55 our Neo4j GraphQL project 04:04 and we'll fire up our Gatsby site 04:12 (mumbles) CB Gatsby site, Gatsby develop. 04:23 Okay, so that'll start the Gatsby GraphQL API 04:27 pull in our Neo4j GraphQL API and then build our sites. 04:33 So where we left off last time. 04:36 Cool, so what are we gonna do today? 04:38 Well, today I want to add two things to our sites. 04:44 The first is images, 04:47 so we will pull data from the Mapillary API 04:54 to find images that are actually 04:57 showing these points of interest. 04:59 We'll pull that into the Neo4j GraphQL API, 05:02 and then pull those images into Gatsby. 05:06 And then the second thing that we'll do 05:08 is some of our points of interest 05:12 have references to Wikipedia pages. 05:16 And so what we'll do is see how we can pull some information 05:22 from Wikipedia to add as content 05:26 into our Gatsby travel guide sites. 05:30 So if we take a look, 05:33 point of interest connected to, 05:38 let me make this a little bigger first there we go, 05:40 connected to OSM tag nodes, where exists 05:53 t.wikipedia, return some of those tags, 06:02 (mumbles) tags with S, there we go. 06:06 So for example here's, 06:10 remember we have our point of interest nodes 06:13 that those are the representation of the OpenStreetMap node 06:19 that has things like the latitude and longitude, 06:23 the timestamp when it was created, that kind of thing, 06:26 that maps to the OpenStreetMap way 06:29 that we can use for routing and things like that. 06:31 And then there's a tags node that has 06:35 kind of arbitrary key value pairs with information 06:37 for each point of interest. 06:39 And so some of these have links to Wikipedia pages. 06:45 So this is a statue of Balto. 06:55 So if you go to Wikipedia, Wikipedia. 07:05 And let's go to our Wikipedia page 07:13 and if we search for Balto, 07:17 you can see that oh, Balto is a sled dog 07:19 and that there's his statue in Central Park. 07:23 So we wanna be able to read about this kind of information 07:25 in our travel guide. 07:28 Okay cool, so those are the two things 07:31 that we're going to do today. 07:37 Let's make sure first of all, 07:39 that we can see our site Localhost 8000. 07:50 Cool, so last time this is where we left off, 07:53 we were pulling in just a list on the main page 07:56 of all of our points of interest. 07:58 And then we created a Gatsby page where we're using 08:05 the OSM node ID. 08:08 So basically the unique ID for each point of interest 08:13 is the slug in the URL for that page 08:17 and we're bringing in the tags from the OSM tags node 08:23 associated with that point of interest 08:25 and showing that if we go look at the code here, 08:34 here's that tags field that we added last time 08:38 to our GraphQL API that is doing that traversal in Cypher 08:45 to populate this data. 08:50 And then in our Gatsby site, 08:53 if we look at the blog post template, 08:58 which this is the React component 09:00 that we're using to define basically this page. 09:07 So this blog post template basically is rendered 09:12 for each one of these pages. 09:16 In our case each one of these points of interest 09:19 that we've pulled from the database. 09:23 So to render this blog post page, we run this GraphQL API, 09:27 this GraphQL query against the Gatsby GraphQL API, 09:32 and then that data then gets passed in as props 09:37 to this blog post template React component, 09:40 it's the data prop. 09:42 And then we use that to render this page, 09:48 passing the data from the GrapghQL query in as props 09:54 to populate the content. 09:55 So what we're gonna do is first update our GraphQL API 10:00 to pull in some third party data from the Mapillary API, 10:05 which is kind of like a crowdsourced Google Street View 10:08 to find some of these images. 10:11 And then we'll display that image 10:13 on each point of interest page. 10:19 The other thing I wanna do is make sure we have our 10:25 Gatsby GraphQL API up and running, which we do. 10:34 So there's two different GraphQL APIs 10:36 that we're gonna be working with. 10:38 One is the Gatsby GraphQL API, 10:42 so when we run Gatsby develop 10:43 that starts this Gatsby GraphQL API, 10:46 and then runs queries against that 10:49 and uses that to render our site. 10:53 But we also have which we started over here 10:58 so running on port localhost 3003, 11:07 this is our Neo4j GraphQL API 11:16 that is talking directly to our Neo4j Sandbox instance. 11:20 And what we've done is using the, 11:23 I think it's gatsby-source-graphql. 11:26 We did this last time, the gatsby-source-graphql plugin 11:32 to basically stitch this Neo4j GraphQL API 11:40 into the Gatsby QraphQL API, 11:42 sort of under this POI field here. 11:49 So if we look at the POI query fields, 11:54 it's giving us points of interests. 11:59 So basically stitching in our Neo4j GraphQL API 12:03 into the GraphQL API that Gatsby uses to render content, 12:06 which is pretty neat, that shows the flexibility of GraphQL. 12:13 Okay cool, so here's our sites 12:15 let's go find some images to add into this. 12:20 So I like to use Mapillary. 12:28 Someone asked to zoom in a little bit, 12:30 Yeah, I'll remember to do that 12:31 so we'll make this page a bit bigger 12:37 and can zoom in here and zoom a little bit on the code. 12:42 Is that better? 12:43 Yeah, thanks for reminding me. 12:49 Okay, so Mapillary is kind of like 12:53 crowdsourced Google Street View. 12:56 So the idea is you download the iPhone app 13:01 or you can also use GoPros and other hardware 13:04 but you download the app and then go around take pictures, 13:09 upload those to Mapillary 13:11 and Mapillary sort of stitches together, 13:14 a bunch of crowdsourced photos of the world, right? 13:19 And so you can sort of see the type of photos 13:24 that people are uploading. 13:25 It's also adding this data to OpenStreetMap. 13:31 So mapping where roads and trails and things like that exist 13:35 along with these photos but Mapillary also has an API, 13:39 which is what we're gonna use today 13:43 that allows us to give a latitude and longitude 13:47 and say, "Hey, find me some pictures 13:52 at this latitude and longitude." 13:55 And then we can display that in our application, 13:59 which is pretty cool, they also have an embeddable widgets, 14:05 which we might look at as well, 14:06 but for now, we'll just start with the photo. 14:07 So if you look at the documentation, 14:15 we are interested in finding images. 14:21 So we're gonna use this search images in point 14:25 passing in a location, so in this case close to us, 14:30 we wanna say, find me a picture that's close to 14:33 this latitude and longitude. 14:38 And then the other one that's important is this look at, 14:41 so not only do I want the picture 14:43 to be close to some latitude and longitude, 14:46 I want the camera to be pointed at the thing 14:49 that I wanna look at. 14:52 And we can also specify our radius 14:55 so we can say only show me pictures that are up close. 14:58 So hopefully for these points of interest in Central Park, 15:02 I'm gonna guess that Central Park 15:05 is pretty well covered with Mapillary 15:08 I guess we'll find out. 15:12 And I'm imagining people 15:13 are wanting to map these points of interest 15:15 like, "Hey, here's a cool fountain 15:17 let's get that on Mapillary." 15:21 Okay cool, so the first thing we need to do 15:24 is register an application. 15:27 If you saw when we were previously working on 15:31 the real estate application 15:35 that we did in a previous stream, 15:38 we called that Willow. 15:40 We used the Mapillary there to find images of houses 15:46 I'll drop a link to this in the chat too. 15:49 So this is a previous project we worked on 15:51 all the recordings from the streams are available there. 15:56 So anyway, use Mapillary previously 15:58 to find images of houses and that, 16:00 that seemed to work okay for some cases 16:03 of course, there were some houses that we couldn't find 16:06 but that's okay. 16:10 Cool, so let's register our application. 16:14 This is Central Park, 16:18 which is a travel guide of Central Park. 16:25 Company name, company website and callback URL 16:32 I don't think we really need that 16:36 because we're not gonna do any of the OATH stuff, 16:41 but just in case we'll put that in there. 16:44 So by default we have access to all the public entities 16:47 for searching images and stuff. 16:48 We don't need to add any scopes 16:50 'cause our application is not going to say like 16:56 upload photos on my behalf or so on. 17:00 So let's register that. 17:05 So the question in the chat is the Willow project finished? 17:10 Yeah, that's a good question. 17:12 It's not, 17:15 I mean in an application like this 17:17 you could probably keep adding features and improvements. 17:24 I'll say it's functionally complete 17:27 so we went all the way up to adding authorization 17:33 and showing how to do mutations for adding listings 17:37 and saving properties. 17:40 There's some things I think we could add to it. 17:43 I wanted to take a look at some recommendation queries 17:48 so that we have the saved list 17:50 can we find other properties 17:53 that you might be interested in? 17:55 And I think we can improve our map component a little bit. 17:58 So yeah, I think we might come back to the Willow project 18:02 at some point to add some of those features, 18:06 but I wanted to kind of kick off a new project 18:10 with this Gatsby project since we were getting up to, 18:15 we had 10, 11 sessions from Willow. 18:19 So didn't want people to get bored with us 18:21 working on the same project, but yeah, if you clone it 18:25 and build it, it should be functionally complete 18:30 to give you a fairly good idea of what's going on there. 18:42 Okay, so we've created our application, 18:46 we've got a client ID, so let's copy that. 18:50 That's what we're going, 18:52 I don't know if I should have a copy of that. 18:53 That's what we're going use to authenticate 18:58 using this search images URL, 19:09 because I already did this with Willow, 19:12 maybe we'll skip ahead a little bit and just take a look at, 19:22 (mumbles) 19:24 and maybe we'll just take a look at how we do this 19:28 to see how we create the URL for that search end point. 19:34 And so if we take a look at the source, 19:39 we can see here that we're creating a customer resolver. 19:44 Let me make this a bit bigger, 19:47 a customer resolver for the photos field 19:51 on the property type, so in GraphQL these resolvers 19:59 are the functions responsible for fetching data 20:04 from the data layer for a given GraphQL request. 20:09 And when we're using neo4j-graphql.js. 20:12 So over here if we look at the code 20:14 we have for our Gatsby sites so far 20:19 in Neo4j GraphQL index, 20:24 all we've done here is defined some type definitions 20:28 and then call make augmented schema, 20:31 which is actually generating the resolvers for us. 20:33 So we haven't needed to create any resolvers yet 20:37 this neo4j-graphql.js library knows how to fetch that data 20:42 from the database without us having to write resolvers. 20:46 But there may be some custom logic that we wanna implement, 20:51 where we may need to add code, some things we can't do 20:59 maybe with the Cypher directive. 21:01 We'll look at later on when we add data with Wikipedia, 21:06 we'll look at how we can add custom logic 21:08 using the Cypher directive. 21:12 But for this Mapillary one, 21:15 we're going to implement a custom resolver. 21:19 Anyway, you can see what we're doing here 21:21 is using this request URL 21:28 to look for Mapillary photos, 21:32 so let's take an example of one of these 21:36 just so we can see what's going on 21:41 and we'll try to do this in the browser. 21:44 So we have a few parameters here. 21:49 So per page that's just the number of photos 21:51 we're gonna bring back. 21:53 I dunno let's do 10. 21:55 We need to specify a radius, 21:59 let's say maybe a hundred and this is gonna be in meters 22:02 a hundred meters and then we need a latitude and longitude. 22:11 Let's do location, latitude, longitude. 22:18 Let's get some points, 22:19 we can start off with, 22:21 (mumbles) and just this first one is fine. 22:25 So our latitude, it's going to be this 22:40 and longitude, it's gonna be this, 22:57 and I'm gonna copy these two, 23:00 those are separated by comma, I'll copy those two 23:03 because this is for the close to parameter. 23:09 But then we also need to specify that same point 23:11 for the look at query parameters. 23:19 Since I wanna make sure that the camera 23:20 is pointed at that point, not just close to it, 23:24 and then we need our client ID let's grab that again. 23:42 Okay, so that should be a valid request 23:45 to the Mapillary API to find some photos of, 23:52 what is this the Jacqueline Kennedy Onassis Reservoir. 23:56 So here's the JSON result that comes back, this is GeoJSON. 24:02 So we can see here that we have some features 24:08 so each feature is in this case gonna be a photo 24:13 and we have a bunch of meta-information about the photo. 24:15 So it was taken with an Apple iPhone X, 24:21 the angle of the camera, the Mapillari user that created it. 24:26 Oh yeah, let's zoom in here, thanks for reminding me 24:33 and the latitude and longitude where the photo is taken 24:38 but we need, 24:40 we don't have a URL for the photo what we have as a key. 24:43 So if we go back, how he did this before, 24:47 so there's different size images that we can create 24:51 from the Mapillary API. 24:55 So we just need to grab the photo key 24:59 so like the idea of the photo, drop that in here, 25:05 640 I think should be a good enough size for us. 25:12 And there's our image of the Jacqueline Kennedy Onassis 25:17 Reservoir in Central Park, 25:19 which yup, that looks like a reservoir to me. 25:25 Cool, so that one worked out pretty good. 25:26 So let's add this to our, 25:35 oops, don't need all of those, 25:39 exit, exit, don't need all those terminals, 25:42 just trying to minimize that a bit. 25:44 Let's add this now to our GraphQL API. 25:48 So what we wanna do is first add it to the type definitions. 25:52 So we have a point of interest that has some fields 25:59 let's add photos that will be, 26:05 previously we added a photos type when we did this 26:10 for the real estate project, 26:13 because I was thinking we could return some of the metadata 26:16 and maybe do some post filtering or something 26:19 but I think here we're just really interested in the URL 26:22 of the photos. 26:24 So we'll just return a string list field for photos. 26:32 And then because we're implementing a custom resolver, 26:38 we're gonna add the Neo4j ignore directive. 26:43 There's a bit more information about what this is 26:45 in the GRANDstackDocs. 26:49 If we take a look at the directives page, 26:53 this should be good, yes this page lists all the directives 26:57 that you can use with neo4j-graphql.js. 27:02 And Neo4j ignore is used to exclude fields or types 27:05 from the Cypher querry generation process 27:08 when implementing a custom resolver. 27:10 So this just sort of says, 27:12 "Hey, don't try to look in the database 27:15 for a property called photos on the point of interest nodes, 27:19 'cause there isn't going to be one." 27:21 And instead we're going to implement a custom resolver 27:27 which is like this. 27:30 Cool, so we haven't created, 27:33 we'll just do this all in the index.js file 27:37 that keeps things simple. 27:38 So we haven't created a resolver map yet, so let's do that. 27:41 So resolvers, this is going to be a map of functions. 27:51 So every field on every property 27:55 or every field and every type rather has a resolver, 28:00 in some cases, there's just a default resolver 28:04 which looks for the matching name of the field 28:11 returned by the previous resolver. 28:14 So these resolvers are called in a nested fashion. 28:17 For example, when we run this point of interest query, 28:22 we start at the point of interest field on the query type 28:30 and then we call the nested resolvers 28:35 iterating through the selection set. 28:36 So we call the name resolver, not the location resolver. 28:40 Then we call the latitude and longitude resolver 28:43 on the location field. 28:44 Now with neo4j-graphql.js because it's generating 28:49 a single database query from the root. 28:53 So most of the time this generated 28:56 point of interest resolver, so the root query field 29:00 is going to look ahead to see the full selection set 29:05 and generate the database query that we're interested in 29:10 and then it's the default resolvers 29:13 that actually get called to just select the name, location, 29:16 latitude, longitude field 29:19 from what's returned by the root resolver. 29:26 So anyway, so that's why we structure 29:30 our resolver map this way. 29:33 So this will be the resolver on the point of interest type 29:44 and then specifically the photos field. 29:49 And this is gonna be really similar to 29:51 what we did for the real estate for Willow 29:56 in the search app. 29:59 So it's gonna be an async function 30:01 'cause we're gonna make a network request 30:03 and there are two things past, 30:07 or say two things that we're interested in 30:09 that are passed into this resolver. 30:12 The first in which we're gonna call POI here. 30:17 That is going to be what has been resolved so far 30:23 for this object, so if we take a look at this query, 30:28 so we said that this point of interest query field, 30:32 the resolver for that is gonna fetch a bunch of data 30:34 from the database. 30:36 So all of that data that's already been fetched 30:39 is going to be passed into this photo's resolver 30:42 in this POI, 30:43 so POI, we're gonna be able to 30:47 access the latitude and longitude of the point of interest 30:52 that we are going to be fetching photos for. 30:58 And then in args these are gonna be any arguments 31:01 of the query fields, 31:08 or not at the query field of this photos field rather. 31:11 And we didn't define any arguments in the typedefs, 31:15 let's go ahead and add some, let's add a, 31:22 how about a first that is gonna be an integer 31:28 and let's make that 10, so by default we'll grab 10 photos. 31:35 And then the other thing we can parameterize is the radius, 31:39 which will also be an integer that's at that to a hundred. 31:42 So remember that's the radius of the point 31:47 that we're passing for Mapillary to say, 31:49 find photos that were taken within in this case, 31:52 a hundred meters of this location. 32:01 And then those will be passed into our resolver function 32:04 in this args object. 32:10 Okay, and then what we're gonna do 32:11 is build up this request URL. 32:14 I'm just gonna copy this directly since we looked at 32:19 what that looks like for the Mapillary API. 32:24 So you can see here we're substituting a Mapillary key, 32:30 let's change this, it's not gonna be property, 32:34 location, longitude. 32:35 It's gonna be POI since we changed (mumbles) 32:41 poi.location.longitude, poi.location.latitude 32:49 and poi.location.langitude, poi.location.latitude 33:00 and then args.radius and args.first, cool. 33:08 Then our response, and it's gonna be a call we'll use axios, 33:19 no need to install that, you can install axios, 33:23 axios is a nice package for making network requests 33:28 has a nice simple API. 33:32 And then that's gonna give us an object like this 33:36 that's gonna come back from the Mapillary API. 33:39 So the thing that we're interested in are the features. 33:45 So response.data.features, so this array, 33:51 this array of feature objects, and then we'll map over that. 33:59 So features.map, so for each element in that features array 34:11 the thing we're gonna return (mumbles) 34:21 the thing we're gonna return is going to be this URL. 34:28 So the URL of our photo images, mapillary.com, 34:38 v.properties.key, so that is this value. 34:44 So we want to substitute this value in to this scheme 34:49 for where these photos live on the Mapillary domain, 34:55 thumb 640 JPEG. 35:02 And then when we call make argement schema, 35:05 we're passing our typedefs 35:06 and now we're gonna pass our resolvers. 35:11 Cool, the other thing we need to do 35:13 is add our Mapillary API key to our environment variables, 35:23 which it looks like we already did, cool. 35:30 So now if we run NPM run start, 35:41 if we add photos, I need to refresh that 35:51 so that a GraphQL (indistinct) picks up the typedefs. 35:59 And axios is not defined. 36:01 Oh right, let's import that axios rules require axios 36:11 and let's try that again. 36:15 And now our query is gonna be a little bit slower 36:19 to resolve because now we need to go out 36:23 to the Mapillary API for each point of interest 36:27 that we're bringing back. 36:28 So that's gonna take a little bit of time. 36:31 So here's a photo from Mapiillary of 36:36 the Jacqueline Kennedy Onassis Reservoir 36:39 that should be the one that we looked at earlier, 36:40 I think it was the first one that grabbed. 36:42 Yeah, so that looks good. 36:45 What else do we have? 36:46 Let's find something else interesting. 36:49 The Pond, Richard Morris, I think that's a statue, NYPD, 37:01 Christopher Columbus, The Pool. 37:05 Let's look at The Pool, let's see how we do here. 37:10 So here's another photo of a point of interest. 37:14 Hmm, that one, maybe not so much 37:17 that someone riding on a bike maybe 37:20 anyway, we'll have to see if we get some good photos 37:24 when we add these into our Gatsby site, 37:28 which is what we can do now, 37:30 so let's switch over now to our Gatsby site. 37:37 So a question from James, 37:40 what are the benefits of using Gatsby 37:42 over just GRANDstack templates? 37:45 Yeah, that's a really good question. 37:48 So Gatsby is a static site generator 37:54 I mean, it's really much more than just that 37:59 but basically with Gatsby we can build all of the content 38:06 for our site as static content using GraphQL 38:11 and all the power of GraphQL to stitch together 38:16 content from a bunch of different sources 38:19 and generate static contents, which we can then serve. 38:25 So that means that since it's static content, 38:29 it's going to load really, really fast. 38:33 It doesn't have to hit a database or an API 38:36 to load each page and Gatsby also has a bunch of plugins, 38:45 I think they have an interesting 38:48 sort of visionary statements. 38:55 Whereas I've seen this, yeah, they're conceptual guide, 38:58 I think lays this out 38:59 and if you look at the Gatsby Core Philosophy 39:05 that talks about kind of the building blocks 39:10 in content mesh for creating a web pages. 39:17 So there's this plugin ecosystem 39:20 so far we've looked at gatsby-source-graphql 39:23 for bringing in third party, GraphQL APIs 39:28 but they also have, so for example, 39:29 gatsby-image that has plugins for optimizing image sizes 39:36 and that sort of thing. 39:38 So there's a lot in Gatsby that we don't have out of the box 39:45 in GRANDstack or just in React 39:49 I would say fundamentally the main difference though, 39:51 is that you're building static content. 39:55 So think of like server-side rendering sort of 40:00 pulling in the content for that via GraphQL. 40:05 later on when we get to routing though 40:07 we'll need to be able to do a sort of dynamic queries 40:11 to our GraphQL API so that we can find some routes 40:16 that are gonna be dependent on where the user is, 40:19 so we'll see how to do that as well. 40:22 Another question from the chat, 40:26 I was playing around with GRANDstack, 40:29 liked everything about it until the moment 40:30 I try to get everything up and running in Google Cloud, 40:35 love to have a template for that. 40:37 Yeah, that's a good point. 40:43 If you look at the GRANDstack Starter it has, 40:50 it is all set up to be deployed to Netlify or Vercel. 40:58 So there's vercel.json and netlify.toml 41:02 that has all the build instructions configured 41:04 for deploying to either of those two services. 41:08 For GCP, my colleague David wrote an example 41:17 let's see if I can find it for using Google Cloud Run. 41:26 yeah, here we go. 41:30 So this article shows how to use Google Cloud Run 41:34 to deploy a GRANDstack application, so that might be, 41:42 let's say that's certainly one way to do it on GCP 41:44 I'll drop that link in the chat, that's one way to do it. 41:50 You could also, I think use, 41:53 well Google Cloud Run is kind of like a containerized, 41:58 serverless endpoint, so it sort of allows you 42:01 to create serverless functions in this case, 42:08 basically just by wrapping a container. 42:10 So in this example, we're running Neo4j GraphQL 42:15 in a container and I think you could also do this 42:19 as a Google Cloud function, 42:25 but yeah, that also works with Google Cloud Runs. 42:27 So anyway, hopefully that is a helpful resource 42:33 for getting a GRANDstack app running on GCP. 42:37 All the codes should be linked in that blog post as well 42:40 If you take a look at that. 42:44 Cool, so we've talked a bit about the difference of Gatsby 42:46 and just sort of a regular GRANDstack app 42:52 talked a bit about Google Cloud Run. 42:56 Right, what we wanted to do now 42:59 is go back to our blog post template 43:06 and let's update our GraphQL query to include photos. 43:18 And where should we put our photos? 43:26 So let's look at our site here, so if we look at, 43:34 so this is the page, 43:37 I don't think it makes sense to put photos on the main page 43:40 'cause what I wanna do and I think we should be able 43:42 to get to this in the next session 43:43 is replace this page with a map 43:47 that has all of these points of interests, 43:50 annotated on the map with the markers, 43:52 So you can see where these things are. 43:55 So we won't put any photos on the front page 43:57 I think maybe what we should do is just put one photo 44:01 on this blog page maybe just below the h1 here, 44:09 maybe that's a good place, so image source, 44:20 is going to be post.photos and that is an array 44:27 so we just want the first photo. 44:43 (mumbles) tag, 44:50 I don't know, let's be a little more specific in our alts, 44:55 since we can say that this, 45:06 photo of post.name. 45:15 Okay, it says cannot query field photos, 45:17 oh right, we needed to restart our Gatsby develop 45:25 need to restart Gatsby so that it picks up the change 45:30 in our Neo4j GraphQL API. 45:41 So I got an error, oh, let's do, 45:46 I ran into this last time. 45:47 Let's do a Gatsby clean, 45:49 so Gatsby I think caches a few things, 45:56 I think the gatsby-source-graphql plugin, 46:03 I think caches the introspection query for the GraphQL API. 46:11 So this is now gonna be a bit slower to build all our pages 46:15 because now for every point of interest, 46:20 it's going to the Mapillary API and getting 10 photos 46:25 actually, we can change that, 46:28 so we're not gonna need 10 photos. 46:32 Let's just get the first one 46:34 because we're only showing one image. 46:38 So no point in fetching a whole bunch. 46:45 So while this is building, 46:49 feel free to ask any questions you have in the chat 46:59 and while this is building 47:00 is there anything else we want to tweak? 47:14 I think this should be good for photos 47:18 so we're gonna show one photo for each point of interest 47:22 on the detail page. 47:27 And if this works, then we should be ready to move on 47:31 to adding Wikipedia data. 47:39 We missed the medium link, any chance of resharing it? 47:42 Yeah, do you mean the link to the Google Cloud Run? 47:49 I gotta close that, Google Cloud Run, GRANDstack. 47:55 This one, drop that in the chat. 48:09 Cool okay, so that finished, 48:13 so now if we refresh there we go, 48:16 now we've got this image of the Jacqueline Kennedy Onassis 48:22 Reservoir in our page. 48:24 Let's look at some other examples here, 48:28 some Alice in Wonderland. 48:29 Oh cool, that one's pretty good. 48:30 So we're zoomed in on the Alice in Wonderland statue. 48:38 Some fountains, 48:41 some of these I think are gonna be better than others 48:43 'cause Mapillary we're meant to be kind of mapping the area 48:52 and sort of stitching together a map not necessarily, 48:59 oh, that one, that a broken URL. 49:05 That's no good. 49:11 Group of Bears right here we're out in traffic. 49:20 North Woods okay, so anyway, 49:24 so we've got now at least a picture 49:28 so we can see what the point of interest looks like. 49:31 So that's neat. 49:34 Let's now see how we can add Wikipedia data. 49:44 I just saved my, what's my change there. 49:46 Oh, my change was just us, just grabbing one photo. 49:51 Okay, so we can see now 49:53 that some of these have Wikipedia tags, 49:58 we said that's stored in the database 50:01 on these OSM tags nodes. 50:06 So what we wanna do is go out to Wikipedia 50:12 and pull in some text that we can then bring in 50:16 to our Gatsby page. 50:21 There's a few different ways to do that. 50:27 So, if we look at a Wikipedia page, 50:39 alright, hold on the Black-Scholes option pricing model, 50:43 sure lets go down to look at it, 50:44 so we could maybe scrape the Wikipedia page directly 50:57 so if we sort of inspect the content here, 51:00 I think everything starting down from 51:06 div that has the content text ID, 51:14 we could do that using, you can actually do this in Cypher 51:18 and APOC using APOC load HTML. 51:25 So APOC is a standard library of procedures for Neo4j 51:33 so things that are ways to extend a Cypher basically. 51:39 So here we're actually fetching some data 51:43 from a Wikipedia page and grabbing some specific elements 51:55 in this case we are grabbing all of the h2s 52:03 So we could do that, Wikipedia also has an API, 52:09 which I took a look at and I think, 52:17 look at this page, 52:20 so this is an example of getting the contents of a page 52:25 through the MediaWiki API. 52:33 I think this is an example, 52:41 so here we're getting the unparsed text. 52:46 I think, yeah, here we go if use the Parse API 52:50 we can get actual HTML content. 52:55 So here we could just fetch that content 53:03 and load that in our Gatsby page since it's HTML. 53:09 So a question in the chat, 53:11 do you know if or how Neo4j can be used to communicate 53:14 with other static data in Gatsby, for example, 53:19 data coming from Gatsby plugin Shopify 53:22 and Neo4j would be great for product recommendations, 53:24 I'm not sure the best way to go about that use case. 53:31 Hmm, that's an interesting one. 53:33 So in that case, I'm guessing Gatsby plugin Shopify 53:40 is adding content into the Gatsby GraphQL API. 53:49 So could you then query that GraphQL API 54:02 to maybe load some data into Neo4J 54:05 maybe that would be a way to go about that? 54:10 I'm not sure but definitely something interesting 54:12 to look at. 54:15 So I do agree that it seems like Neo4j should be great 54:19 for adding product recommendations to a Gatsby site 54:23 using in this case, if you're already using 54:25 the Shopify plugin. 54:29 Yeah, that would be, if we can look at that 54:33 in a future stream, seeing how we can make that happen. 54:40 That would be very cool. 54:46 So looking at our Wikipedia example, 54:53 I think this is gonna be kind of the route we wanna go down 54:58 is using the API because then we're not having to deal with 55:04 sort of scraping the webpage. 55:10 We can just query the API and get HTML 55:13 that we can then render directly. 55:17 Yeah, maybe querying the Shopify GraphQL API directly. 55:21 Yeah, that might be a way to do it. 55:23 So maybe stitching together the Shopify GraphQL API 55:29 and a Neo4j GraphQL API and then using 55:39 gatsby-source-graphql to pull that combined data into Gatsby 55:44 kind of the way that we're doing it now. 55:46 Yeah, that might be a better way to do it. 55:49 What does the Shopify plugin do, 55:51 is that just wrapping the Shopify GraphQL API 55:55 and making it available in Gatsby? 55:57 I haven't looked at that, let's take a look at that. 56:01 Gatsby plug in Shopify. 56:09 Source plug in for pulling data into Gatsby 56:11 from Shopify stores via the Storefront API. 56:24 Hmm yeah, that definitely looks interesting to explore. 56:31 Yeah, add that to the list of Gatsby, 56:35 Neo4j things we can look at. 56:41 Okay cool, so we have an example of, 56:46 here we're pulling through the API pet-door 56:51 data from Wikipedia. 56:53 Let's see how we can do this in Cypher. 56:59 So what we're going to do is, 57:06 in our Neo4j GreaphQL project, 57:15 we're going to add a field, I'll do it here 57:21 so it's separate from our other Cypher directive fields 57:23 so it's a bit more readable what's going on 57:26 so let's just call this Wikipedia. 57:29 It's gonna be a string, just a string of HTML 57:33 and we're going to create a Cypher directive field. 57:42 and remember the Cypher directive fields 57:45 are where we can define some custom logic. 57:48 So we're saying the results for this field. 57:52 So Wikipedia on the point of interest type 57:54 is gonna be bound to the results of some Cypher query 57:57 and in that Cypher query, we're gonna use APOC Load HTML 58:02 to go out to the, 58:04 or load JSON rather to go out to the Wikipedia API 58:10 and fetch that data from the Wikipedia page if it exists. 58:16 So basically if we take a step back 58:19 of what we're covering today is we're basically covering 58:22 two different ways to build versus to add custom logic 58:29 to our GraphQL API. 58:31 The first way was by creating a custom resolver 58:38 and we did that to go to the Mapillary API 58:42 and now we're going to see how to do that 58:45 something very similar in this case using just Cypher 58:49 from within GraphQL. 58:51 So I think we could have queried the Mapillary API 58:55 using Apoc as well 58:56 but it's good to look at different ways to do things. 59:02 Okay, so let's write this in, 59:11 Neo4j first to make sure this is gonna work. 59:12 So it's gonna be something like this, 59:15 where we're matching a point of interest 59:20 that is connected to an OSM tags node, 59:29 where t.wikipedia, 59:38 lets do a count and see how many of these 59:40 actually have Wikipedia pages, 35. 59:45 So not all of our points of interests have Wikipedia pages, 59:49 that's okay. 59:59 Let's bring through for now, 60:00 Just one sort to do this just for one point of interest, 60:04 just to get this working 60:07 and what we're going to do 60:10 if we look in the documentation for APOC load JSON 60:19 we're going to say, let's see if there's an example here, 60:26 APOC load JSON can be passed on to URL 60:31 and we get back a (indistinct) an object 60:38 where the Jason data has been parsed and is in that object 60:50 named the value in this case, 60:52 there's some other flavors of APOC load JSON 60:54 so we can do JSONparams 60:57 if we have some query parameters we wanna add, 61:07 we can also set the headers 61:10 if we have like an authorization header 61:12 or something like that 61:13 in our case, the API is open 61:15 so that should be pretty easy. 61:16 I'll drop a link to the documentation for that 61:20 in the chat here if you wanna see more about using APOC. 61:31 Okay, so we're gonna say call APOC load JSON 61:37 and then we have the URL 61:48 (mumbles) 61:55 not that one, not this one, here it is. 62:00 Yeah, so let's see what I want 62:03 let's tweak the format here and format equals JSON 62:13 here me go. 62:18 Cool, so I think that is the URL we want, 62:31 in the page. 62:32 So page is the query parameter for the page we wanna load. 62:38 So let's pull that off to the end here 62:46 and what we'll do, page equals, 62:50 let's do some string concatenation 62:53 and we're gonna add on t.wikipedia. 62:58 So that t.wikipedia that has the Wikipedia page 63:05 so there's our Balto examples so for this case 63:08 the Wikipedia page we want is just Balto. 63:12 Okay, and then I'm gonna say yield value 63:20 so this call to APOC load JSOM is gonna yield value, 63:24 which is gonna be a map or an object 63:28 which is gonna be this 63:31 and then what we wanna return is value.parse.text 63:38 'cause we want that HTML value.parse.text 63:44 and let's see if that works. 63:47 Failed to invoke illegal character. 63:52 Yeah okay, so what we need to do is urlencode 63:55 see we've got spaces, so we need to urlencode that, 64:00 which we can do in APOC no problem, 64:04 APOC has a urlencode I think it is 64:13 APOC Neo4j urlencode. 64:20 Yeah, we got APOC text urlencode. 64:22 So that's going to take out those spaces, 64:26 replace them with valid characters 64:32 that can be used in a URL. 64:37 So apoc.text.urlencode the Wikipedia piece, 64:53 add other closed param in there, 64:57 Let's give that a try, 64:59 Cool, here we go. 65:00 So now we've got back a bunch of HTML for, 65:07 I don't know, whichever the first point of interest was, 65:12 (mumbles) Jacqueline Kennedy Onassis Reservoir again. 65:16 Okay cool, so let's copy this 65:19 'cause its gonna be pretty close to what we wanna add 65:21 to our GraphQL API. 65:29 Let's get this decent indentation so it makes sense. 65:34 Okay, so instead of matching on P point of interest, 65:39 we have a keyword on these Cypher directive fields, this, 65:45 so this refers to the current object that we're resolving. 65:49 So this point of interest node that we're resolving, 65:53 traverse out to that OSM tags node 65:57 let's get the direction on here, 66:04 I'll just go I guess from the point of interest node 66:06 to Balto, so the reason I don't add an a relationship type 66:15 to this pattern is I think we had a different 66:20 relationship for some reason 66:22 yeah, so some of these are called associated. 66:28 Sometimes it's associated sometimes tags, 66:31 I can't remember what the difference is 66:32 I think it has to do with how we had that structured 66:37 during the import anyway, but that's fine we'll leave it. 66:40 Leave no direction and no relationship that's fine. 66:47 Well, we don't want to match both places, there we go. 66:50 Okay, so from this node to find the OSM tags, 67:00 only in the case where we have t.ikipedia, 67:03 we can leave this here because we're only interested in one 67:07 I don't know if we find multiple tag nodes 67:08 I don't think we have any of those 67:09 but we wanna make sure we don't 67:12 and then this bit stays the same. 67:17 Okay, so we'll save that jump to GraphQL Playground 67:22 we'll refresh that so it picks up our changes 67:26 and let's zoom in a bit 67:28 and now instead of photos, will give me Wikipedia. 67:33 So now we should get, 67:35 if any of these first 10 have Wikipedia pages, 67:38 remember only 35 or so of our Wikipedia 67:42 or of our points of interest had Wikipedia pages linked 67:46 but this first one does so that looks good. 67:50 This is Jacqueline Kennedy Onassis Reservoir again. 67:55 So The Pond doesn't have one, 67:58 The Conservatory Water that does have one cool, good. 68:02 Okay, so we're getting some results here, cool. 68:05 So now we wanna add that too our Gatsby page. 68:12 So we'll jump back to the blog post template 68:16 and we will update our GraphQL query 68:23 to now include the Wikipedia field. 68:32 And where should we put this? 68:35 Well, how about, 68:42 I think here just you can create a div after our tags. 68:53 So we have this HTML content, how are we gonna render that? 69:01 Well, Gatsby has a way to, 69:09 dangerously set inner HTML 69:11 so dangerously set in your HTML is a JavaScript thing 69:17 I'm not sure but this comes from React. 69:27 Not sure what specific things Gatsby does for this 69:35 but let's give this a try. 69:41 So we've got our div and we can say 69:42 dangerously set inner HTML is just gonna be post.wikipedia 69:56 So let's save that and we're going to need to do a Gatsby 70:03 clean to remove the cache because we updated 70:07 our Neo4j GraphQL API 70:12 and Gatsby needs to pick up those changes. 70:19 And this will take a little bit to build 70:22 because we need to go out to the Mapillary API 70:27 I think at this point that's probably what's taking 70:30 the most time for building these pages, 70:36 lets scroll through the chats, see if I missed to anything. 70:40 Yeah, so we talked about looking at product recommendations 70:45 for the Shopify API, that would be a great idea 70:48 definitely add that to the list 70:50 we talked about deploying to Google using Cloud Run, 70:59 talked a bit about the Willow project, 71:02 cool, don't think I missed anybody but again, 71:04 if you have any questions or thoughts 71:06 feel free to drop those in the chat. 71:15 Okay, so what we should expect to see when this is loaded 71:22 is we should expect the main index page should be the same 71:26 so just showing us less the points of interest 71:29 but now when we click on one of those pages, 71:32 in addition to the photo that we just added from Mapillary, 71:36 we should see some content from Wikipedia. 71:40 So since this is a travel guide 71:43 those are two pretty important things, 71:44 we wanna see pictures 71:45 of what this point of interest looks like 71:49 and then wanna be able to read any information 71:54 that we have to learn the significance 71:58 of this point of interest that we're gonna go look at. 72:01 If anyone has any ideas for things we could add 72:04 to this project definitely let us know. 72:09 We could take a look at things like adding user reviews, 72:14 we're definitely going to add the routing, 72:16 So we'll definitely add a map view and routing 72:21 in the next one. 72:24 But then beyond that, we can start to brainstorm 72:28 other things we might wanna add. 72:33 Okay, did we finish? 72:34 Got some warnings here from I guess my poorly written JSX 72:40 I've left a few things, dangerously set 72:48 HTML must be in the form. 72:51 Oh yeah, I forgot oops. 72:54 forgot this underscore, underscore, HTML. 73:03 Right, that's object, so that needs another curly brace. 73:11 Did that finish already? 73:12 Yeah, there we go. 73:16 Okay, so here's our list of things 73:19 if we look at and one in detailed view, we see our image, 73:22 we see our tags coming from the database, 73:27 and now we're pulling in some data from Wikipedia 73:32 so we can learn all about how this reservoir was built. 73:43 Cool, where are some others? 73:46 This one does not have a Wikipedia page, what else? 73:53 Crime Busters Rock nope, 73:55 that does not have a Wikipedia page. 74:00 What else should? 74:05 Alexander Hamilton statue? 74:07 Yup, he's got some Wikipedia info. 74:10 This just, I guess the general Wikipedia page 74:14 for Alexander Hamilton not necessarily just the statue. 74:18 I guess that's good to include that in a travel guide. 74:21 That's kind of the thing I would want to know more about 74:31 what else? 74:32 Group of bears nope. 74:41 Samuel Morse cool, so there's a bit ones you can see 74:45 lots of info from Wikipedia image of the statue, 74:49 now we have a map to see where it is 74:52 links to other points of interest in Central Park 74:56 that's cool. 75:02 This one is a redirect so I guess that, 75:07 link is a bit out of date from OpenStreetMap. 75:12 Cool well, that looks okay 75:15 for some of the points of interest that indicated 75:21 that they had a Wikipedia page. 75:29 From the chat, I thought it'd be cool 75:31 show how schema stitching works 75:32 between Shopify API and Neo4j. 75:35 Yeah, we can definitely try to get that working. 75:43 Cool well, I think that we have accomplished 75:45 what we set out to do today. 75:48 We said our goals were to add photos from the Mapillary API 75:53 which we've got here and to add data from Wikipedia 75:58 for the pages that have them, which we've got here. 76:02 So I think that's good enough for today. 76:07 I'll push these changes up to GitHub 76:10 and we'll also share the recording of this stream as well. 76:17 So just to kind of reiterate what we did today. 76:22 Well, we added two things to our Neo4j GraphQL API. 76:28 The first was adding photos, 76:35 so we added this photos fields 76:39 and added the Neo4j ignore directive on it 76:44 saying we're gonna build a custom resolver, 76:47 which we did here and in the customer resolver, 76:51 we called out to the Mapillary API 76:54 to find photos at some point. 76:59 So wherever this point of interest is 77:01 find a photo of that point of interest. 77:05 And then we added that into the Gatsby page 77:12 by just adding that to the GraphQL query, 77:17 and then rendering that just in a image tag 77:22 and then the other thing we did 77:23 is using now a Cypher directive field in our GraphQL API, 77:31 we use the APOC load JSON procedure to call out 77:36 to the Wikipedia API to fetch Wikipedia data 77:41 for the point of interest and then similarly in Gatsby, 77:49 well, down here, we just add that field 77:52 to the Gatsby GraphQL query in our blog templates 77:57 that is responsible for fetching the data 77:59 for each blog post. 78:01 And we use the dangerously set inner HTML function here 78:09 I guess that's a prop in React to params 78:13 to set the HTML content of this div 78:17 so we add in some HTML content there 78:20 with the data from Wikipedia. 78:25 Cool, so next time I wanna take a look 78:29 at adding a map component and there's two stages to that. 78:36 I guess one, 78:38 I don't know if we'll get through it all in one stream, 78:39 but one, I wanna add a map component here with markers 78:43 for each of these points of interest 78:45 so we can see where they are 78:47 and then I want to be able to look at routing. 78:52 So I'm at one point of interest I wanna get to the other, 78:56 we'll use some graph algorithms to do efficient 79:00 shortest path routing using the OpenStreetMap data 79:04 that we have 'cause this dataset that we looked at 79:08 is quite rich if you look at the db.schema, 79:15 it's quite rich, we have a lot more 79:17 than just point of interest. 79:18 We have basically all of the OpenStreetMap ways 79:22 so all of the roads, bike paths, sidewalks and so on, 79:27 we have all of that in this data set for Central Park, 79:32 and we have a routing graph on top of that. 79:35 And we can then do very efficient graph algorithms 79:40 for finding the best way from one point of interest 79:43 to the other and we wanna show that on the map as well. 79:46 So anyway, that's where we will pick up next time, 79:50 which will be next Thursday at 2:00 PM Pacific. 79:55 So thanks for joining today 79:57 and we'll see you all next week, cheers.
Subscribe To Will's Newsletter
Want to know when the next blog post or video is published? Subscribe now!