Social graph tutorial
Social graphs are a common feature of many modern mobile, gaming, and web applications. This page will walk you through the process of creating a simple social graph. The example we’ll build here can support features such as timelines, suggested followers, and ErdÅ‘s numbers.
This tutorial assumes that you have completed the Fauna Shell quick start. |
-
Start Fauna Shell
In a terminal, start Fauna Shell by running:
fauna shell my_db Starting shell for database my_db Connected to https://db.fauna.com Type Ctrl+D or .exit to exit the shell
-
Create a collection and index to represent people
The first collection that we need to create is
people
, to represent the users in our social graph. Users could be players in a game, subscribers to an author’s articles, or coworkers in a professional network.CreateCollection({ name: "people" })
Then, we need to create an index on peoples' names. The index allows us to refer to people with names like "alice" rather than refs like
Ref(Collection("people"), 1)
. Also, let’s use the unique constraint to ensure that multiple users don’t have the same name.CreateIndex({ name: "people_by_name", source: Collection("people"), terms: [{ field: ["data", "name"] }], unique: true })
-
Create a collection and index to represent relationships
The connection between people is called a "relationship". Relationships are directional: a person may follow another person without requiring the second person to reciprocate the relationship. Let’s call the first person a "follower" of the "followee".
CreateCollection({ name: "relationships" })
Then, we need to create an index on the
relationships
collection which is the core of our social graph. This index allows us to easily answer questions such as "who follows person A?" or "who follows person A, but not person B?"CreateIndex({ name: "followers_by_followee", source: Collection("relationships"), terms: [{ field: ["data", "followee"] }], values: [{ field: ["data", "follower"] }] })
-
Populate the graph
Now that we have collections representing the people in our graph (
people
) and their relationships to each other (relationships
), we can begin populating the graph.First, let’s create four users: Alice, Bob, Carol, and Dave. Notice that we added an index on the field
["data", "name"]
for thepeople
collection. Later on, we can use that index to find the four people in our graph.Foreach( ["Alice", "Bob", "Carol", "Dave"], Lambda("name", Create(Collection("people"), { data: { name: Var("name") } }) ) )
In the following queries, we use this query pattern:
Get(Match(Index("people_by_name"), "Alice"))
This query uses the index
people_by_name
to find the first person with the name "Alice". We can be sure that the first person returned is the one we’re looking for because of the uniqueness constraint we set when we created the index.The first relationship that we are going to create is between Alice and Bob. We’ll create a relationship with Alice as the follower, and Bob as the followee — in plain English, this says "Alice follows Bob."
Create( Collection("relationships"), { data: { follower: Select("ref", Get(Match(Index("people_by_name"), "Alice"))), followee: Select("ref", Get(Match(Index("people_by_name"), "Bob"))) } } )
Next, let’s add a relationship in the other direction: Bob follows Alice.
Create( Collection("relationships"), { data: { follower: Select("ref", Get(Match(Index("people_by_name"), "Bob"))), followee: Select("ref", Get(Match(Index("people_by_name"), "Alice"))) } } )
Let’s use the index
people_by_name
to find all users named either "Alice" or "Bob" and add a relationship to Carol — i.e. Carol follows both Alice and Bob.Let( { follower: Select("ref", Get(Match(Index("people_by_name"), "Carol"))) }, Foreach( Paginate( Union( Match(Index("people_by_name"), "Alice"), Match(Index("people_by_name"), "Bob") ) ), Lambda("followee", Create( Collection("relationships"), { data: { followee: Var("followee"), follower: Var("follower") } } ) ) ) )
Finally, let’s add a relationship meaning, "Dave follows Alice."
Create( Collection("relationships"), { data: { followee: Select("ref", Get(Match(Index("people_by_name"), "Alice"))), follower: Select("ref", Get(Match(Index("people_by_name"), "Dave"))) } } )
-
Explore the graph
We now have four users and relationships among them in our social graph:
-
Alice follows Bob,
-
Bob follows Alice,
-
Carol follows both Alice and Bob,
-
Dave follows Alice.
Using our index
followers_by_followee
, we can now answer questions about these relationships.To find a person’s follower list, we use a
Select
query to extract theref
of each follower, like this:Select("ref", Get(Match(Index("people_by_name"), "Alice")))
-
Find Alice’s followers
We can use a
ref
as the term in aMatch
query on our relationship index. This query returns all of the refs of Alice’s followers:Paginate(Match(Index("people_by_name"), "Alice"))
The result should look something like this (the identifiers for your documents are distinct):
{ data: [ Ref(Collection("people"), "236350059641307648") ] }
Our application might be able to interpret refs, but they’re hard for a human to comprehend, so let’s
Map
over the set of refs, get each person’s document, and select their name out of the document.Map( Paginate(Match(Index("people_by_name"), "Alice")), Lambda("person", Select(["data", "name"], Get(Var("person"))) ) )
The result should be:
{ data: [ 'Alice' ] }
Putting it all together gives us a human-friendly list of the names of Alice’s followers:
Map( Paginate( Match( Index("followers_by_followee"), Select("ref", Get(Match(Index("people_by_name"), "Alice"))) ) ), Lambda("person", Select(["data", "name"], Get(Var("person"))) ) )
The result should be similar to (the order might differ):
{ data: [ 'Bob', 'Dave', 'Carol' ] }
-
Find Alice’s OR Bob’s followers
Now that we’ve seen how to list a single person’s followers, we can use that knowledge to ask questions about the connections between follower lists.
For example, the union of the follower lists tells us who follows either person. Here, we ask who follows Alice or Bob:
Map( Paginate( Union( Match( Index("followers_by_followee"), Select("ref", Get(Match(Index("people_by_name"), "Alice"))) ), Match( Index("followers_by_followee"), Select("ref", Get(Match(Index("people_by_name"), "Bob"))) ) ) ), Lambda("person", Select(["data", "name"], Get(Var("person"))) ) )
The result should be similar to:
{ data: [ 'Alice', 'Bob', 'Dave', 'Carol' ] }
-
Find Alice’s AND Bob’s followers
The intersection of follower lists finds common followers among people. This query asks who follows Alice and Bob:
Map( Paginate( Intersection( Match( Index("followers_by_followee"), Select("ref", Get(Match(Index("people_by_name"), "Alice"))) ), Match( Index("followers_by_followee"), Select("ref", Get(Match(Index("people_by_name"), "Bob"))) ) ) ), Lambda("person", Select(["data", "name"], Get(Var("person"))) ) )
The result should be:
{ data: [ 'Carol' ] }
-
Find people who follow Alice, but NOT Bob
The difference of follower lists tells us who follows some people, but not others. This is how we ask for followers of Alice, but not Bob:
Map( Paginate( Difference( Match( Index("followers_by_followee"), Select("ref", Get(Match(Index("people_by_name"), "Alice"))) ), Match( Index("followers_by_followee"), Select("ref", Get(Match(Index("people_by_name"), "Bob"))) ) ) ), Lambda("person", Select(["data", "name"], Get(Var("person"))) ) )
The result should be similar to:
{ data: [ 'Bob', 'Dave' ] }
-
Conclusion
In this tutorial, we have modeled a simple social graph using FaunaDB’s collections and indexes. We have used the query language to ask questions about the connections in the graph that are useful building blocks of social features in mobile apps, games, and web applications.
Was this article helpful?
We're sorry to hear that.
Tell us how we can improve!
documentation@fauna.com
Thank you for your feedback!