Despite the “Graph” in the name, GraphQL is mostly used to query relational databases, object models or APIs. But it is really easy to support GraphQL endpoints from graph databases too. In this talk, I’ll demonstrate how we implemented a GraphQL extension for the Neo4j graph database. It uses the GraphQL schema definition map arbitrary GraphQL queries into single graph queries and runs them against the data in the Graph database. Using directives in the schema, we added some cool features that are transparent to the end user like computed fields and auto-generated mutations and query types. That allows you to create GraphQL APIs of some complexity without writing a single line of code.
I will show how to use the Neo4j-GraphQL extension, by creating an endpoint for the Game of Thrones dataset, and how we then can use our well-known tools (GraphiQL, apollo-client, graphql-cli, voyager) to interact with it.
3. AN API OF ICE AND
FIRE
https://anapioficeandfire.com/About
https://github.com/joakimskoog/AnApiOfIceAndFire
4. An API of
Ice and Fire
• Data sourced fromWiki of Ice And Fire (AWOIAF)
• Well documented .Net powered API
• Data on
– Books (5)
– Characters (2400)
– Houses (444)
• 100 entries per page
5. An API of
Ice and Fire
– https://www.anapioficeandfire.com/api/characters/1303
12. LOAD VIA
API
• Iterate over batches of pages
• Data cleanup
• Replace URLs with IDs
• Throttle
• INSERT COMPLEX SCRIPT HERE
unwind range(1,43) as page
call apoc.util.sleep(1)
with page, 'characters' as type
call apoc.load.jsonArray('https://www.anapioficeandfire.com/api/'+type+'?pageSize=50&page='+page) yield value
with apoc.convert.toMap(value) as data
MERGE (p:Person {id:split(data.url,"/")[-1]})
SET
p += apoc.map.clean(data, ['allegiances','books','father','spouse','mother'],['',[''],[]]),
p.books = [b in data.books | split(b,'/')[-1]],
p.name = colaesce(p.name,head(p.aliases))
FOREACH (a in data.allegiances | MERGE (h:House {id:split(a,'/')[-1]}) MERGE (p)-[:SWORN_TO]->(h))
FOREACH (f in case coalesce(data.father,"") when "" then [] else [data.father] end | MERGE (o:Person {id:split(f,'/')[-1]})
MERGE (o)-[:PARENT_OF {type:'father'}]->(p))
FOREACH (f in case coalesce(data.mother,"") when "" then [] else [data.mother] end | MERGE (o:Person {id:split(f,'/')[-1]})
MERGE (o)-[:PARENT_OF {type:'mother'}]->(p))
FOREACH (f in case coalesce(data.spouse,"") when "" then [] else [data.spouse] end | MERGE (o:Person {id:split(f,'/')[-1]})
MERGE (o)-[:SPOUSE]-(p))
return p.id, p.name;
13. BETTER!
FULL
JSON
DATA
• Full JSON data in Github Repository
• Load all characters and houses in one go
• No URL conversion
• But lowercasing keys
• Offline and much faster
call apoc.load.jsonArray('https://raw.githubusercontent.com/joakimskoog/AnApiOfIceAndFire/master/data/houses.json') yield value
with apoc.convert.toMap(value) as data
with apoc.map.clean(data, [],['',[''],[],null]) as data
with apoc.map.fromPairs([k in keys(data) | [toLower(substring(k,0,1))+substring(k,1,length(k)), data[k]]]) as data
MERGE (h:House {id:data.id})
SET
h += apoc.map.clean(data, ['overlord','swornMembers','currentLord','heir','founder','cadetBranches'],['',[''],[],null])
FOREACH (id in data.swornMembers | MERGE (o:Person {id:id}) MERGE (o)-[:ALLIED_WITH]->(h))
FOREACH (s in data.seats | MERGE (seat:Seat {name:s}) MERGE (seat)-[:SEAT_OF]->(h))
FOREACH (id in data.cadetBranches | MERGE (b:House {id:id}) MERGE (b)-[:BRANCH_OF]->(h))
FOREACH (id in case data.overlord when null then [] else [data.overlord] end | MERGE (o:House {id:id}) MERGE (h)-[:SWORN_TO]->(o))
FOREACH (id in case data.currentLord when null then [] else [data.currentLord] end | MERGE (o:Person {id:id}) MERGE (h)-[:LED_BY]->(o))
FOREACH (id in case data.founder when null then [] else [data.founder] end | MERGE (o:Person {id:id}) MERGE (h)-[:FOUNDED_BY]->(o))
FOREACH (id in case data.heir when null then [] else [data.heir] end | MERGE (o:Person {id:id}) MERGE (o)-[:HEIR_TO]->(h))
FOREACH (r in case data.region when null then [] else [data.region] end | MERGE (o:Region {name:r}) MERGE (h)-[:IN_REGION]->(o))
return h.id, h.name;
17. MISSING DATA !?
MATCH (p:Person)
WHERE size(p.tvSeries) > 1
AND NOT exists((p)-[:PARENT_OF]-())
RETURN p LIMIT 10;
• Walder
• The waif
• High Septon
• MargaeryTyrell
• Tywin Lannister
• Unella
• AemonTargaryen
• AlliserThorne
• Arya Stark
• Asha Greyjoy
20. Code and instructions for
todays presentation
• GitHub
– https://github.com/neo4j-examples/game-of-thrones
• Medium Post
– medium.com/@mesirii/a-game-of-data-and-graphql-97ee2ca297ce
• Graph ofThrones 7 week contest
– https://neo4j.com/blog/graph-of-thrones/
• An API of Ice and Fire
– https://github.com/joakimskoog/AnApiOfIceAndFire
– https://anapioficeandfire.com/About
• GraphQL CLI Load
– https://npmjs.org/package/graphql-cli-load
22. More Fun
with Graphs and
Game of Thrones
• Character Interactions
– https://networkofthrones.wordpress.com/
– :play https://guides.neo4j.com/got
• GraphAnalytics of Character Interactions
– http://www.lyonwj.com/2016/06/26/graph-of-thrones-neo4j-
social-network-analysis/
• NLP on the book texts
– https://graphaware.com/neo4j/2017/07/24/reverse-
engineering-book-stories-nlp.html
• The Maths of Game ofThrones
– https://anthonybonato.com/2016/04/13/the-mathematics-of-
game-of-thrones/
• Kaggle GoT Data (Battles)
– https://tbgraph.wordpress.com/?s=Game+of+Thrones