Build a graph database (Neo4j / Dgraph-style) (16 scenes)
Scene 02 · Nodes, relationships, properties
The property-graph model is four things — nodes, typed relationships, labels, and properties on both — and putting key/values directly ON the edge is the move that relational join-tables and RDF triples can't make cleanly.
Previously

Scene 1's pointer walk beat the join, but we cheated — we never said how a relationship gets to be a thing you can follow. Here's the model that makes it real: relationships are first-class records with their own type and properties, not rows in a join table. Now the obvious question — what does 'first-class' buy you at the byte level that a join row doesn't?

Scene 02
Nodes, relationships, properties
Diagram
One fact — 'Alice KNOWS Bob since 2020' — drawn three ways. RELATIONAL (left): a USER table, a KNOWS junction table for the link, and a SEPARATE attributes table because a join row has nowhere to put the date. RDF (middle): the edge has no internal structure, so to attach the date you must reify — turn the relationship into its own blank node (_:k1) and emit extra triples. PROPERTY GRAPH (right): nodes plus one directed, typed relationship that is a first-class record carrying {since:2020} on the edge itself. Property graph (node/relationship) = data modeled as nodes + first-class typed relationships, both of which carry properties. Label & property = a label is the node's category (:User, :Movie); a property is a key/value entry in the map ({since:2020}) stored directly on a node OR an edge — the move the other two models can't make cleanly.
Alice KNOWS Bob since 2020RELATIONALUSER1 Alice2 BobKNOWS(a,b)1 → 2KNOWS_ATTRS(1,2) since=2020RDF (triples)alice :knows _:k1 ._:k1 :object bob ._:k1 :since 2020 .PROPERTY GRAPH{since:2020}Alice{name:Alice}BobFRIENDSame fact, three models — the property graph carries the date ON the edge.
One fact: 'Alice KNOWS Bob since 2020.' Watch it drawn three ways, left to right. RELATIONAL needs a junction table for the link AND a separate attributes table just to hold the date. RDF has to reify — the edge becomes its own blank node with extra triples. The PROPERTY GRAPH stores it as a single typed edge, Alice—[:KNOWS]→Bob, with {since:2020} living right on the edge. Count the moving parts: the property graph has the fewest. That right pane is the model the rest of the course is built on.
Implementation
PropertyGraph.record_fact
one edge carries the relationship's own properties
1def record_fact(g, a, rel_type, b, props):
2 # nodes are first-class; so is the relationship
3 g.merge_node(a)
4 g.merge_node(b)
5 # the edge IS a record — properties live ON it
6 g.add_edge(a, b, type=rel_type, props=props)
7 # e.g. add_edge('Alice','Bob','KNOWS',{since:2020})
Relational.record_fact
a link needs a junction row + a separate attrs row
1def record_fact(db, a, rel, b, attrs):
2 db.upsert('USER', a); db.upsert('USER', b)
3 # the link: just the two endpoints
4 db.insert(rel + '(a,b)', (a.id, b.id))
5 # attributes have nowhere to go but a side table
6 db.insert(rel + '_ATTRS', (a.id, b.id, attrs))
RDF.record_fact
reify: the edge becomes a blank node + extra triples
1def record_fact(store, a, rel, b, attrs):
2 bnode = store.fresh_blank_node()
3 store.add(a, rel, bnode) # alice :knows _:k1
4 store.add(bnode, ':object', b) # _:k1 :object bob
5 for k, v in attrs.items():
6 store.add(bnode, k, v) # _:k1 :since 2020
Not sure what to ask? Tap a question — the staff engineer answers in the chat panel.