Chapter 02 · Property Graph Model: Nodes, Relationships, Labels, Properties, Types, and Schema Discipline

Nodes and Relationships as First-Class Entities: Direction, Identity, and Traversal Semantics

Make graph structure precise: stored direction, relationship type, durable domain identity, and measurable traversal/cardinality behavior.

Beginner → Intermediate105–125 minutesIdentity/direction/traversal labNeo4j 2026.07.1 Community · Cypher 25Last reviewed: September 2026

Learning outcomes

AtlasMart's Chapter 01 graph already contains customers, devices, orders, and products. The next modeling question is whether connected facts behave the way developers think they do. If a supplier supplies a product, an order contains a product, and a shipment fulfills an order, the direction and identity of those relationships change which traversals are meaningful, which duplicates are bugs, and which business keys survive export, restore, and migration.

01

Distinguish node identity, business identity, element identity, relationship identity, direction, type, and traversal direction.

02

Explain why every stored Neo4j relationship has exactly one type and a direction even though Cypher may match it undirected.

03

Use stable constraint-backed domain keys instead of elementId() as durable AtlasMart identifiers.

04

Measure direction and degree/cardinality from a small graph rather than infer them from a diagram.

05

Diagnose modeling mistakes where strings or duplicate relationships hide domain semantics.

Chapter 02 continuity contract

Continue Chapter 01's free/local baseline: Neo4j Community 2026.07.1, database neo4j, explicit CYPHER 25 in version-sensitive examples, local container atlasmart-neo4j, Bolt 127.0.0.1:7687, HTTP 127.0.0.1:7474, and disposable password atlasmart-course-2026. Existing AtlasMart identifiers use Community-supported uniqueness constraints. This chapter adds :Category, :Supplier, :Store, and :Shipment identities and makes graph-model contracts observable.

Execution and edition note

The generation environment does not provide a running Docker daemon or Neo4j server, so commands are documentation-checked but not presented as captured output. Community supports the uniqueness constraints used in the mandatory lab. Current property-existence, property-type, and key constraints are Enterprise-only; when those stronger contracts are discussed, the Community path uses validation queries plus application/import checks rather than fabricating successful Community DDL.

Nodes and relationships are first-class graph entities

A Neo4j node is a graph entity with zero or more labels and zero or more properties. A relationship connects exactly two nodes, has exactly one relationship type, always has a stored direction, and may also have properties. That means (Customer)-[:PLACED]->(Order) is not shorthand for an order.customerId property. It is independently addressable graph structure that Cypher can bind, filter, return, count, and traverse.

Direction should express domain meaning. AtlasMart reads (Customer)-[:PLACED]->(Order) naturally as “customer placed order.” Reversing the arrow in storage would not make the database incorrect, but it would make query conventions and mental models harder to maintain. At query time, MATCH (c)-[:PLACED]-(o) deliberately ignores direction and can match either orientation. That query syntax does not make stored relationships directionless.

Concept What Neo4j guarantees/represents Modeling consequence
Node A structural graph entity with labels/properties Use for things/events that need independent identity, connections, or lifecycle.
Relationship Exactly two endpoints, one stored direction, exactly one relationship type, optional properties Use when the connection itself matters to traversal or carries facts.
Label Classification attached to a node Useful for matching/index/constraint scope; not itself uniqueness.
Relationship type Classification of one relationship Should carry domain meaning such as PLACED, not generic RELATED_TO by default.
elementId() Identifier with transaction/DBMS-scoped guarantees; mapping is not durable across transactions/environments Expose only when needed for implementation evidence; use application-generated IDs for business identity.

Observe direction instead of trusting a picture

Cypher · extend the Chapter 01 fixture with explicit directions
CYPHER 25CREATE CONSTRAINT category_id IF NOT EXISTS FOR (c:Category) REQUIRE c.categoryId IS UNIQUE;CREATE CONSTRAINT supplier_id IF NOT EXISTS FOR (s:Supplier) REQUIRE s.supplierId IS UNIQUE;MERGE (cat:Category {categoryId:'CAT-CAMERAS'}) SET cat.name='Cameras'MERGE (s:Supplier {supplierId:'SUP-1001'}) SET s.name='Northwind Optics'MERGE (p:Product {productId:'P-1001'})MERGE (p)-[:IN_CATEGORY]->(cat)MERGE (p)-[:SUPPLIED_BY {preferred:true}]->(s);

The relationship SUPPLIED_BY is stored from product to supplier because the dominant AtlasMart questions start at a product. That is a model convention, not a database requirement. The following query proves orientation from the stored relationship itself:

Cypher · prove endpoints, type, direction, and implementation id
CYPHER 25MATCH (p:Product {productId:'P-1001'})-[r:SUPPLIED_BY]->(s:Supplier)RETURN p.productId AS fromProduct,       type(r) AS relationshipType,       s.supplierId AS toSupplier,       r.preferred AS preferred,       elementId(r) AS implementationElementId;

elementId(r) is useful evidence that the relationship is a first-class element, but current Neo4j documentation explicitly warns that applications should not treat the returned value as a durable cross-transaction business key. If a relationship needs durable business identity—for example, a contractual supply agreement—model an application-generated agreement ID or reify the agreement as a node when its lifecycle warrants it.

Directed match versus undirected match

Cypher · the same stored edge under two query intents
CYPHER 25MATCH (:Product {productId:'P-1001'})-[:SUPPLIED_BY]->(s:Supplier)RETURN s.supplierId AS directedSupplier;CYPHER 25MATCH (:Product {productId:'P-1001'})-[:SUPPLIED_BY]-(other)RETURN labels(other) AS otherLabels, other.supplierId AS supplierId;

The first pattern states an AtlasMart semantic expectation: from product to supplier. The second asks only whether an adjacent SUPPLIED_BY relationship exists. On this fixture both can return the supplier, but they express different contracts. An undirected match is useful when orientation is intentionally irrelevant; overusing it can hide accidentally reversed writes.

Boundary case: direction is not authorization or cardinality.

Storing (Customer)-[:PLACED]->(Order) does not prevent two customers from pointing at the same order, nor does it prevent a shipment from being connected to several orders. Direction describes the edge orientation; cardinality and business invariants still need validation, constraints where available, transaction logic, or a different model.

Deliberately wrong model: opaque foreign-key strings

Suppose AtlasMart creates (o:Order {orderId:'O-5002', customerId:'C-1001'}) but no PLACED relationship. The graph now contains a string that refers to another entity but no traversable connection. Every query must manually re-join the property values, and graph algorithms/paths cannot treat that reference as an edge.

Cypher · expose and repair the opaque reference
CYPHER 25MERGE (o:Order {orderId:'O-5002'}) SET o.customerId='C-1001';MATCH (o:Order) WHERE o.customerId IS NOT NULL AND NOT (:Customer {customerId:o.customerId})-[:PLACED]->(o)RETURN o.orderId, o.customerId;CYPHER 25MATCH (o:Order {orderId:'O-5002'}), (c:Customer {customerId:o.customerId})MERGE (c)-[:PLACED]->(o)REMOVE o.customerId;MATCH (c:Customer)-[:PLACED]->(o:Order {orderId:'O-5002'})RETURN c.customerId, o.orderId;

The repair turns the connection into graph structure and removes the redundant foreign-key-like property once it is no longer required. If another system owns the customer ID reference and synchronization requires preserving it, keep it deliberately and document which copy is authoritative rather than calling duplication inherently wrong.

Lab: measure identity, direction, and degree

Cypher · stable identities for Chapter 02 entities
CYPHER 25 CREATE CONSTRAINT store_id IF NOT EXISTS FOR (s:Store) REQUIRE s.storeId IS UNIQUE;CYPHER 25 CREATE CONSTRAINT shipment_id IF NOT EXISTS FOR (s:Shipment) REQUIRE s.shipmentId IS UNIQUE;CYPHER 25 MERGE (st:Store {storeId:'ST-TEH-01'}) SET st.name='AtlasMart Central';CYPHER 25 MERGE (sh:Shipment {shipmentId:'SH-7001'}) SET sh.status='IN_TRANSIT';CYPHER 25 MATCH (p:Product {productId:'P-1001'}),(st:Store {storeId:'ST-TEH-01'}) MERGE (p)-[r:STOCKED_AT]->(st) SET r.onHand=14;CYPHER 25 MATCH (o:Order {orderId:'O-5001'}),(sh:Shipment {shipmentId:'SH-7001'}) MERGE (o)-[:FULFILLED_BY]->(sh);
Cypher · count direction/cardinality evidence
CYPHER 25MATCH (c:Customer)-[:PLACED]->(o:Order)RETURN c.customerId, count(o) AS ordersPlaced ORDER BY c.customerId;CYPHER 25MATCH (p:Product)-[r:STOCKED_AT]->(st:Store)RETURN p.productId, st.storeId, r.onHand ORDER BY p.productId, st.storeId;CYPHER 25MATCH (n)RETURN labels(n) AS labels, count(*) AS nodes ORDER BY labels;

Verification checklist: domain IDs are unique under named constraints; every PLACED edge points customer→order; every STOCKED_AT edge points product→store; elementId() is never copied into a business-key property; and expected degree/counts are written down so later changes can be regression-tested.

Check your understanding

  1. Why can an undirected MATCH succeed even though every relationship is stored with direction?
  2. Why is elementId() useful for evidence but poor as an AtlasMart business key?
  3. What semantic information is lost when a customer reference is stored only as an order.customerId string?
  4. Does relationship direction enforce one customer per order?
  5. When might a relationship deserve its own domain identifier or intermediate node?
Review the answers

1. Cypher can deliberately ignore orientation while matching; this does not alter the stored direction.

2. Its mapping to elements has limited guarantees outside a transaction and internal identifiers can be reused; application-generated IDs are the durable identity surface.

3. The connection is no longer first-class graph structure, so traversal/path operations cannot follow it directly and integrity becomes application-side reference logic.

4. No. Direction is orientation, not cardinality or uniqueness.

5. When the connection has its own lifecycle, independent references, many properties, participants beyond two endpoints, or temporal/history requirements that make it a first-class business fact.

Summary and next step

Nodes and relationships are structural graph elements, but durable business identity remains an application/model contract. Stored direction is real even when a query matches an edge undirected. Chapter 02 now has stable category, supplier, store, and shipment identities plus explicit relationship semantics that later Cypher can rely on.

Next, inspect what may legally be stored as a property, how lists/maps differ, and why Neo4j's null represents missing/unknown state rather than a stored null property.

Authoritative references

Keep knowledge open

Help the academy stay free and grow.

If these tutorials save you time, a small donation supports new lessons, technical review, diagrams, examples, and long-term maintenance.

ETHEthereum / ERC-20 only
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0

Send only Ethereum or ERC-20 compatible assets to this address.