5.11. Implementing newsfeeds in a graph

Implementation of newsfeed or timeline feature is a frequent requirement for social applications. The following exmaples are inspired by Newsfeed feature powered by Neo4j Graph Database. The query asked here is:

Starting at me, retrieve the time-ordered status feed of the status updates of me and and all friends that are connected via a CONFIRMED FRIEND relationship to me.

Query 

MATCH (me { name: 'Joe' })-[rels:FRIEND*0..1]-(myfriend)
WHERE ALL (r IN rels WHERE r.status = 'CONFIRMED')
WITH myfriend
MATCH (myfriend)-[:STATUS]-(latestupdate)-[:NEXT*0..1]-(statusupdates)
RETURN myfriend.name AS name, statusupdates.date AS date, statusupdates.text AS text
ORDER BY statusupdates.date DESC LIMIT 3

To understand the strategy, let’s divide the query into five steps:

  1. First Get the list of all my friends (along with me) through FRIEND relationship (MATCH (me {name: 'Joe'})-[rels:FRIEND*0..1]-(myfriend)). Also, the WHERE predicate can be added to check whether the friend request is pending or confirmed.
  2. Get the latest status update of my friends through Status relationship (MATCH myfriend-[:STATUS]-latestupdate).
  3. Get subsequent status updates (along with the latest one) of my friends through NEXT relationships (MATCH (myfriend)-[:STATUS]-(latestupdate)-[:NEXT*0..1]-(statusupdates)) which will give you the latest and one additional statusupdate; adjust 0..1 to whatever suits your case.
  4. Sort the status updates by posted date (ORDER BY statusupdates.date DESC).
  5. LIMIT the number of updates you need in every query (LIMIT 3).

Result

namedatetext
3 rows

"Joe"

6

"Joe status2"

"Bob"

4

"bobs status2"

"Joe"

3

"Joe status1"

Try this query live create (_0 {`name`:"Bob"}) create (_1 {`date`:1, `name`:"bob_s1", `text`:"bobs status1"}) create (_2 {`date`:4, `name`:"bob_s2", `text`:"bobs status2"}) create (_3 {`name`:"Alice"}) create (_4 {`date`:2, `name`:"alice_s1", `text`:"Alices status1"}) create (_5 {`date`:5, `name`:"alice_s2", `text`:"Alices status2"}) create (_6 {`name`:"Joe"}) create (_7 {`date`:3, `name`:"joe_s1", `text`:"Joe status1"}) create (_8 {`date`:6, `name`:"joe_s2", `text`:"Joe status2"}) create _0-[:`STATUS`]->_1 create _0-[:`FRIEND` {`status`:"CONFIRMED"}]->_3 create _1-[:`NEXT`]->_2 create _3-[:`STATUS`]->_4 create _3-[:`FRIEND` {`status`:"PENDING"}]->_6 create _4-[:`NEXT`]->_5 create _6-[:`STATUS`]->_7 create _6-[:`FRIEND` {`status`:"CONFIRMED"}]->_0 create _7-[:`NEXT`]->_8 MATCH (me {name: 'Joe'})-[rels:FRIEND*0..1]-(myfriend) WHERE ALL(r in rels WHERE r.status = 'CONFIRMED') WITH myfriend MATCH (myfriend)-[:STATUS]-(latestupdate)-[:NEXT*0..1]-(statusupdates) RETURN myfriend.name as name, statusupdates.date as date, statusupdates.text as text ORDER BY statusupdates.date DESC LIMIT 3

Here, the example shows how to add a new status update into the existing data for a user.

Query 

MATCH (me)
WHERE me.name='Bob'
OPTIONAL MATCH (me)-[r:STATUS]-(secondlatestupdate)
DELETE r
CREATE (me)-[:STATUS]->(latest_update { text:'Status',date:123 })
WITH latest_update, collect(secondlatestupdate) AS seconds
FOREACH (x IN seconds | CREATE (latest_update)-[:NEXT]->(x))
RETURN latest_update.text AS new_status

Dividing the query into steps, this query resembles adding new item in middle of a doubly linked list:

  1. Get the latest update (if it exists) of the user through the STATUS relationship (OPTIONAL MATCH (me)-[r:STATUS]-(secondlatestupdate)).
  2. Delete the STATUS relationship between user and secondlatestupdate (if it exists), as this would become the second latest update now and only the latest update would be added through a STATUS relationship; all earlier updates would be connected to their subsequent updates through a NEXT relationship. (DELETE r).
  3. Now, create the new statusupdate node (with text and date as properties) and connect this with the user through a STATUS relationship (CREATE (me)-[:STATUS]->(latest_update { text:'Status',date:123 })).
  4. Pipe over statusupdate or an empty collection to the next query part (WITH latest_update, collect(secondlatestupdate) AS seconds).
  5. Now, create a NEXT relationship between the latest status update and the second latest status update (if it exists) (FOREACH(x in seconds | CREATE (latest_update)-[:NEXT]->(x))).

Result

new_status
1 row
Nodes created: 1
Relationships created: 2
Properties set: 2
Relationships deleted: 1

"Status"

Try this query live create (_0 {`name`:"Bob"}) create (_1 {`date`:1, `name`:"bob_s1", `text`:"bobs status1"}) create (_2 {`date`:4, `name`:"bob_s2", `text`:"bobs status2"}) create _0-[:`STATUS`]->_1 create _1-[:`NEXT`]->_2 MATCH (me) WHERE me.name='Bob' OPTIONAL MATCH (me)-[r:STATUS]-(secondlatestupdate) DELETE r CREATE (me)-[:STATUS]->(latest_update {text:'Status',date:123}) WITH latest_update, collect(secondlatestupdate) as seconds FOREACH(x in seconds | CREATE (latest_update)-[:NEXT]->(x)) RETURN latest_update.text as new_status