1. Pengenalan Graph Database & Neo4j
Graph Database adalah database yang menyimpan data dalam bentuk graph — jaringan node (titik) dan relationship (garis penghubung). Berbeda dari database relasional yang menyimpan data dalam tabel, graph database fokus pada hubungan antar data.
Neo4j adalah graph database paling populer di dunia. Dibangun sejak 2007, Neo4j menggunakan model Labeled Property Graph dan bahasa query Cypher yang intuitif dan ekspresif.
┌─────────────────────────────────────────────────────────────────┐ │ RELATIONAL DATABASE GRAPH DATABASE │ │ │ │ ┌──────────┐ ┌──────────┐ (Person) │ │ │ Person │ │ Friend │ ●──────● │ │ ├──────────┤ ├──────────┤ /│ │\ │ │ │ id │ │ person_id│ ● │ │ ● │ │ │ name │ │ friend_id│ \│ │/ │ │ │ age │ │ since │ ●──────● │ │ └──────────┘ └──────────┘ │ │ │ │ SELECT p.name, f.name MATCH (p:Person)-[:FRIEND]-> │ │ FROM person p (f:Person) │ │ JOIN friend fr RETURN p.name, f.name │ │ ON p.id = fr.person_id │ │ JOIN person f │ │ ON f.id = fr.friend_id Lebih intuitif & cepat │ │ WHERE p.name = 'Budi'; untuk query hubungan! │ │ │ │ 3 tabel JOIN untuk teman 1 pattern cukup │ └─────────────────────────────────────────────────────────────────┘
Kapan Menggunakan Graph Database?
| Use Case | Mengapa Graph? | Contoh |
|---|---|---|
| Social Network | Hubungan antar user sangat kompleks | Facebook, LinkedIn |
| Recommendation Engine | "Orang yang beli X juga beli Y" | Amazon, Netflix |
| Fraud Detection | Temukan pola transaksi mencurigakan | Bank, payment gateway |
| Knowledge Graph | Hubungan antar entitas dan konsep | Google Knowledge Graph |
| Network/IT Infrastructure | Topologi jaringan dan dependencies | Cloud monitoring |
| Route Planning | Shortest path antar lokasi | Google Maps, GPS |
Setup Neo4j
# ===== Docker (rekomendasi untuk development) =====
docker run \
--name neo4j \
-p 7474:7474 -p 7687:7687 \
-e NEO4J_AUTH=neo4j/password123 \
-e NEO4J_PLUGINS='["apoc"]' \
neo4j:5-community
# Browser: http://localhost:7474
# Bolt protocol: bolt://localhost:7687
# ===== Download langsung =====
# https://neo4j.com/download/
# Community Edition (gratis) sudah cukup untuk belajar
# ===== Neo4j Aura (Cloud, Free Tier) =====
# https://neo4j.com/cloud/aura-free/
# Managed service, tidak perlu instalasi
2. Nodes, Relationships & Properties
Konsep Dasar Property Graph
| Komponen | Simbol Cypher | Penjelasan |
|---|---|---|
| Node | (n:Label) | Entitas (orang, produk, tempat) — punya label dan properties |
| Relationship | -[r:TYPE]-> | Hubungan antar node — selalu punya arah dan tipe |
| Label | :Person | Kategori/tipe node — bisa punya banyak label |
| Property | {name: 'Budi'} | Data key-value pada node atau relationship |
┌─────────────────────────────────────────────────────────────────┐
│ PROPERTY GRAPH MODEL │
│ │
│ {name: 'Budi'} {name: 'Sari'} │
│ ●──────────────●───────────────● │
│ /│ :Person │ │\ │
│ / │ │ │ \ │
│ / │ :KNOWS │ │ \ │
│ / │ {since: │ │ \ │
│ / │ 2020} │ │ \ │
│ ● │ │ │ ● │
│ {name: │ │ │ {name: │
│ 'Rina'}│ │ │ 'Andi'} │
│ :Person│ │ │ :Person │
│ │ │ │ │
│ ●──────────────┘ └──────────● │
│ :WORKS_AT :WORKS_AT │
│ {role: 'Engineer'} {role: 'PM'} │
│ │ │ │
│ ●────────┘ │ │
│ {name: 'TechCorp'} ●──────────────────────┘ │
│ :Company {name: 'StartupXYZ'} │
│ :Company │
│ │
│ Node punya: Label + Properties (key-value) │
│ Relationship punya: Tipe + Arah + Properties │
└─────────────────────────────────────────────────────────────────┘
// =============================================
// MEMBUAT NODES
// =============================================
// Buat node Person dengan properties
CREATE (budi:Person {
name: 'Budi Santoso',
age: 28,
email: 'budi@email.com',
city: 'Jakarta'
})
// Buat node Person kedua
CREATE (sari:Person {
name: 'Sari Dewi',
age: 26,
email: 'sari@email.com',
city: 'Bandung'
})
// Buat node Person ketiga
CREATE (andi:Person {
name: 'Andi Pratama',
age: 30,
email: 'andi@email.com',
city: 'Jakarta'
})
// Buat node Company
CREATE (techcorp:Company {
name: 'TechCorp Indonesia',
industry: 'Technology',
founded: 2015
})
// Buat node Product
CREATE (laptop:Product {
name: 'Laptop Pro X',
price: 15000000,
category: 'Elektronik',
stock: 50
})
CREATE (phone:Product {
name: 'Phone Ultra',
price: 8000000,
category: 'Elektronik',
stock: 200
})
// =============================================
// MEMBUAT RELATIONSHIPS
// =============================================
// Budi kenal Sari sejak 2020
MATCH (budi:Person {name: 'Budi Santoso'})
MATCH (sari:Person {name: 'Sari Dewi'})
CREATE (budi)-[:KNOWS {since: 2020, source: 'university'}]->(sari)
// Sari kenal Andi sejak 2021
MATCH (sari:Person {name: 'Sari Dewi'})
MATCH (andi:Person {name: 'Andi Pratama'})
CREATE (sari)-[:KNOWS {since: 2021}]->(andi)
// Budi juga kenal Andi
MATCH (budi:Person {name: 'Budi Santoso'})
MATCH (andi:Person {name: 'Andi Pratama'})
CREATE (budi)-[:KNOWS {since: 2019}]->(andi)
// Budi bekerja di TechCorp sebagai Engineer
MATCH (budi:Person {name: 'Budi Santoso'})
MATCH (tc:Company {name: 'TechCorp Indonesia'})
CREATE (budi)-[:WORKS_AT {role: 'Senior Engineer', since: 2020}]->(tc)
// Budi membeli Laptop
MATCH (budi:Person {name: 'Budi Santoso'})
MATCH (laptop:Product {name: 'Laptop Pro X'})
CREATE (budi)-[:PURCHASED {
date: date('2026-01-15'),
quantity: 1,
rating: 5
}]->(laptop)
// Budi juga membeli Phone
MATCH (budi:Person {name: 'Budi Santoso'})
MATCH (phone:Product {name: 'Phone Ultra'})
CREATE (budi)-[:PURCHASED {
date: date('2026-03-20'),
quantity: 1,
rating: 4
}]->(phone)
// =============================================
// MERGE: Create if not exists (idempotent)
// =============================================
MERGE (rina:Person {name: 'Rina Wati'})
ON CREATE SET rina.age = 25, rina.city = 'Surabaya'
ON MATCH SET rina.updated_at = datetime()
// MERGE untuk relationship
MATCH (budi:Person {name: 'Budi Santoso'})
MATCH (rina:Person {name: 'Rina Wati'})
MERGE (budi)-[r:KNOWS]->(rina)
ON CREATE SET r.since = 2024
ON MATCH SET r.last_seen = datetime()
3. Cypher — Dasar Query Language
Cypher adalah bahasa query deklaratif untuk Neo4j. Desainnya terinspirasi dari ASCII art — node digambarkan sebagai () dan relationship sebagai --> atau --. Ini membuat query Cypher sangat mudah dibaca.
// =============================================
// MATCH — Mencari nodes
// =============================================
// Ambil semua node Person
MATCH (p:Person)
RETURN p
// Ambil semua node dengan property tertentu
MATCH (p:Person {city: 'Jakarta'})
RETURN p.name, p.age
// Filter dengan WHERE
MATCH (p:Person)
WHERE p.age > 25 AND p.city = 'Jakarta'
RETURN p.name, p.age, p.city
// Ambil semua node (tanpa filter)
MATCH (n)
RETURN labels(n), count(n) AS jumlah
// Ambil relationship tertentu
MATCH (p:Person)-[r:KNOWS]->(friend:Person)
RETURN p.name AS nama, friend.name AS teman, r.since AS kenal_sejak
// =============================================
// RETURN — Menentukan output
// =============================================
// Return specific properties
MATCH (p:Person)
RETURN p.name AS nama, p.age AS umur
ORDER BY p.age DESC
LIMIT 10
// Return distinct values
MATCH (p:Person)-[:WORKS_AT]->(c:Company)
RETURN DISTINCT c.name AS perusahaan
// Return as list
MATCH (p:Person)-[:PURCHASED]->(prod:Product)
RETURN p.name AS nama, collect(prod.name) AS produk_dibeli
// =============================================
// CREATE — Membuat data baru
// =============================================
// Buat node baru
CREATE (u:User {
id: randomUUID(),
name: 'User Baru',
email: 'baru@email.com',
created_at: datetime()
})
RETURN u
// Buat relationship baru
MATCH (a:Person {name: 'Budi Santoso'})
MATCH (b:Person {name: 'Rina Wati'})
CREATE (a)-[:MENTORS {since: 2024}]->(b)
// =============================================
// SET & REMOVE — Update properties
// =============================================
// Update property
MATCH (p:Person {name: 'Budi Santoso'})
SET p.age = 29, p.updated_at = datetime()
RETURN p
// Tambah label baru
MATCH (p:Person {name: 'Budi Santoso'})
SET p:PremiumUser:Verified
// Hapus property
MATCH (p:Person {name: 'Budi Santoso'})
REMOVE p.email
// =============================================
// DELETE — Menghapus data
// =============================================
// Hapus node (harus hapus relationship dulu!)
MATCH (p:Person {name: 'Test User'})
DETACH DELETE p
// Hapus relationship
MATCH (p:Person {name: 'Budi Santoso'})-[r:KNOWS]->(other:Person {name: 'Rina Wati'})
DELETE r
// Hapus semua node & relationships (HATI-HATI!)
// MATCH (n) DETACH DELETE n
Anda tidak bisa menghapus node yang masih punya relationship. Gunakan DETACH DELETE untuk menghapus node beserta semua relationship-nya. Atau hapus relationship terlebih dahulu sebelum menghapus node.
4. Pattern Matching & Traversal
Kekuatan utama graph database adalah pattern matching — mencari pola hubungan antar node. Cypher mendeskripsikan pola ini secara visual menggunakan sintaks yang intuitif.
// =============================================
// BASIC PATTERNS
// =============================================
// Pattern: Siapa yang dikenal Budi?
MATCH (budi:Person {name: 'Budi Santoso'})-[:KNOWS]->(teman)
RETURN teman.name
// Pattern: Teman dari teman (2 hop)
MATCH (budi:Person {name: 'Budi Santoso'})-[:KNOWS*2]->(fof)
RETURN DISTINCT fof.name AS friend_of_friend
// Pattern: Variable length path (1-3 hop)
MATCH (budi:Person {name: 'Budi Santoso'})-[:KNOWS*1..3]->(connected)
RETURN DISTINCT connected.name
// Pattern: Shortest path antara 2 node
MATCH path = shortestPath(
(budi:Person {name: 'Budi Santoso'})-[:KNOWS*]-(target:Person {name: 'Andi Pratama'})
)
RETURN path, length(path) AS degrees_of_separation
// =============================================
// BIDIRECTIONAL PATTERNS
// =============================================
// Relationship tanpa arah (kedua arah)
MATCH (p:Person)-[:KNOWS]-(other:Person)
WHERE p.name = 'Budi Santoso'
RETURN other.name
// Directed: Budi → teman
MATCH (p:Person)-[:KNOWS]->(other:Person)
RETURN p.name, other.name
// =============================================
// MULTI-PATTERN (menggabungkan beberapa pola)
// =============================================
// Budi kenal siapa, dan siapa yang beli produk yang sama
MATCH (budi:Person {name: 'Budi Santoso'})-[:PURCHASED]->(p:Product)
MATCH (other:Person)-[:PURCHASED]->(p)
WHERE other <> budi
RETURN other.name AS pembeli, p.name AS produk
// Budi dan temannya yang bekerja di perusahaan sama
MATCH (budi:Person {name: 'Budi Santoso'})-[:WORKS_AT]->(c:Company)
MATCH (colleague:Person)-[:WORKS_AT]->(c)
WHERE colleague <> budi
RETURN colleague.name AS rekan, c.name AS perusahaan
// =============================================
// OPTIONAL MATCH (LEFT JOIN equivalent)
// =============================================
// Ambil semua person, tampilkan perusahaan jika ada
MATCH (p:Person)
OPTIONAL MATCH (p)-[:WORKS_AT]->(c:Company)
RETURN p.name, COALESCE(c.name, 'Freelance') AS pekerjaan
// =============================================
// EXISTS — Cek keberadaan pattern
// =============================================
// Person yang pernah membeli sesuatu
MATCH (p:Person)
WHERE EXISTS {
MATCH (p)-[:PURCHASED]->(:Product)
}
RETURN p.name
// =============================================
// NEGATION — Pattern yang TIDAK ada
// =============================================
// Person yang TIDAK punya relationship KNOWS
MATCH (p:Person)
WHERE NOT EXISTS {
MATCH (p)-[:KNOWS]->(:Person)
}
RETURN p.name
// Person yang TIDAK pernah membeli apapun
MATCH (p:Person)
WHERE NOT EXISTS {
MATCH (p)-[:PURCHASED]->(:Product)
}
RETURN p.name
5. Aggregation & Filtering
// =============================================
// AGGREGATION FUNCTIONS
// =============================================
// Hitung jumlah teman per orang
MATCH (p:Person)-[:KNOWS]->(teman:Person)
RETURN p.name, count(teman) AS jumlah_teman
ORDER BY jumlah_teman DESC
// Total pembelian per orang
MATCH (p:Person)-[r:PURCHASED]->(prod:Product)
RETURN p.name AS nama,
count(prod) AS jumlah_produk,
sum(prod.price * r.quantity) AS total_belanja,
avg(prod.price) AS rata_harga,
min(prod.price) AS harga_termurah,
max(prod.price) AS harga_termahal
ORDER BY total_belanja DESC
// Collect: kumpulkan values ke list
MATCH (p:Person)-[:KNOWS]->(teman:Person)
RETURN p.name AS nama,
collect(teman.name) AS daftar_teman,
count(teman) AS jumlah
// Count distinct
MATCH (p:Person)-[:PURCHASED]->(prod:Product)
RETURN prod.category AS kategori,
count(DISTINCT p.name) AS jumlah_pembeli
// =============================================
// HAVING equivalent: WHERE after aggregation
// =============================================
// Teman dengan lebih dari 1 hubungan
MATCH (p:Person)-[:KNOWS]->(teman:Person)
WITH p, count(teman) AS jumlah
WHERE jumlah > 1
RETURN p.name, jumlah
// Atau gunakan HAVING pattern dengan WITH
MATCH (p:Person)-[:KNOWS]->(teman:Person)
WITH p, count(teman) AS jumlah_teman
WHERE jumlah_teman >= 2
RETURN p.name, jumlah_teman
ORDER BY jumlah_teman DESC
// =============================================
// WITH: Pipeline chaining
// =============================================
// Step 1: Hitung total belanja per orang
// Step 2: Filter yang belanja > 10jt
// Step 3: Return dengan info lengkap
MATCH (p:Person)-[r:PURCHASED]->(prod:Product)
WITH p, sum(prod.price * r.quantity) AS total
WHERE total > 10000000
RETURN p.name AS nama, total AS total_belanja
ORDER BY total DESC
// =============================================
// UNWIND: Expand list ke rows
// =============================================
// Membuat nodes dari list
WITH ['Jakarta', 'Bandung', 'Surabaya', 'Yogyakarta'] AS kota_list
UNWIND kota_list AS kota
CREATE (k:Kota {name: kota})
RETURN k
// =============================================
// ORDER BY, SKIP, LIMIT
// =============================================
// Pagination di graph
MATCH (p:Person)
RETURN p.name, p.age
ORDER BY p.name ASC
SKIP 0 LIMIT 10 // Halaman 1
// Sorting multi-field
MATCH (p:Person)-[:PURCHASED]->(prod:Product)
RETURN p.name, prod.name, prod.price
ORDER BY p.name ASC, prod.price DESC
6. Advanced: Path, Algorithms & APOC
// =============================================
// PATH QUERIES
// =============================================
// Semua path antara 2 node (max 5 hop)
MATCH path = (a:Person {name: 'Budi Santoso'})
-[:KNOWS*1..5]-
(b:Person {name: 'Andi Pratama'})
RETURN path, length(path) AS panjang
ORDER BY panjang
// All simple paths (no repeated nodes)
MATCH path = allShortestPaths(
(a:Person {name: 'Budi Santoso'})-[:KNOWS*]-(b:Person {name: 'Andi Pratama'})
)
RETURN path
// Ring/cycle detection
MATCH path = (p:Person)-[:KNOWS*3..6]->(p)
RETURN p.name, length(path) AS cycle_length
// =============================================
// GRAPH ALGORITHMS (GDS - Graph Data Science)
// =============================================
// Degree Centrality: Siapa yang paling banyak hubungan?
MATCH (p:Person)
OPTIONAL MATCH (p)-[:KNOWS]-(other:Person)
RETURN p.name, count(other) AS degree
ORDER BY degree DESC
// PageRank: Siapa yang paling "influential"?
// (Memerlukan GDS library)
// CALL gds.pageRank.stream('myGraph')
// YIELD nodeId, score
// RETURN gds.util.asNode(nodeId).name AS name, score
// ORDER BY score DESC
// Community Detection: Grup mana saja?
// CALL gds.louvain.stream('myGraph')
// YIELD nodeId, communityId
// RETURN communityId, collect(gds.util.asNode(nodeId).name) AS members
// Shortest Path dengan Dijkstra
// MATCH (start:Person {name: 'Budi Santoso'}), (end:Person {name: 'Andi Pratama'})
// CALL gds.shortestPath.dijkstra.stream('myGraph', {
// sourceNode: start,
// targetNode: end,
// relationshipWeightProperty: 'weight'
// })
// YIELD index, sourceNode, targetNode, totalCost, path
// RETURN path, totalCost
// =============================================
// APOC LIBRARY (Advanced Procedures)
// =============================================
// Generate data dummy
// CALL apoc.create.node(['Person'], {name: 'Random Person ' + apoc.create.uuid()})
// YIELD node RETURN node
// Export to JSON
// CALL apoc.export.json.all("export.json", {})
// YIELD file, source, format, nodes, relationships
// RETURN file, nodes, relationships
// Path expansion dengan kontrol lebih detail
// CALL apoc.path.expand(
// startNode,
// 'KNOWS|WORKS_AT>', // relationship types
// 'Person|Company', // end node labels
// 1, // min depth
// 5 // max depth
// )
// YIELD path RETURN path
7. Use Case: Social Network & Recommendation
// =============================================
// SOCIAL NETWORK: Rekomendasi Teman
// =============================================
// "People You May Know": teman dari teman yang belum jadi teman
MATCH (me:Person {name: 'Budi Santoso'})-[:KNOWS]->(friend)-[:KNOWS]->(fof:Person)
WHERE NOT (me)-[:KNOWS]->(fof)
AND me <> fof
RETURN fof.name AS rekomendasi,
count(friend) AS mutual_friends,
collect(friend.name) AS via
ORDER BY mutual_friends DESC
LIMIT 5
// =============================================
// RECOMMENDATION ENGINE
// =============================================
// "Orang yang beli produk yang sama juga beli..."
MATCH (me:Person {name: 'Budi Santoso'})-[:PURCHASED]->(p:Product)
MATCH (other:Person)-[:PURCHASED]->(p)
WHERE other <> me
MATCH (other)-[:PURCHASED]->(recommendation:Product)
WHERE NOT (me)-[:PURCHASED]->(recommendation)
RETURN recommendation.name AS produk_rekomendasi,
recommendation.price AS harga,
count(other) AS dibeli_oleh,
collect(DISTINCT other.name) AS pembeli_lain
ORDER BY dibeli_oleh DESC
LIMIT 5
// =============================================
// FRAUD DETECTION: Pola Mencurigakan
// =============================================
// Deteksi: Banyak transaksi cepat dari IP yang sama
MATCH (u:User)-[:USED_IP]->(ip:IP)<-[:USED_IP]-(other:User)
WHERE u <> other
MATCH (u)-[:TRANSACTION]->(t:Transaction)
WHERE t.amount > 5000000
AND t.date > datetime() - duration('P7D')
RETURN u.name AS user_suspect,
ip.address AS shared_ip,
count(t) AS high_value_transactions,
other.name AS other_user
ORDER BY high_value_transactions DESC
// =============================================
// KNOWLEDGE GRAPH: Semantic Search
// =============================================
// "Siapa yang bekerja di bidang Technology di Jakarta?"
MATCH (p:Person)-[:WORKS_AT]->(c:Company {industry: 'Technology'})
WHERE p.city = 'Jakarta'
RETURN p.name, c.name, p.age
ORDER BY p.age
// "Apa saja produk yang dibeli orang dari Jakarta?"
MATCH (p:Person {city: 'Jakarta'})-[:PURCHASED]->(prod:Product)
RETURN prod.name AS produk,
prod.category AS kategori,
count(p) AS jumlah_pembeli,
avg(prod.price) AS rata_harga
ORDER BY jumlah_pembeli DESC
// =============================================
// ANALYTICS: Degree Distribution
// =============================================
// Distribusi jumlah koneksi per user
MATCH (p:Person)
OPTIONAL MATCH (p)-[r]-()
WITH p, count(r) AS connections
RETURN connections AS jumlah_koneksi,
count(p) AS jumlah_orang
ORDER BY connections
8. Best Practices & Kapan Menggunakan
Neo4j vs Relasional: Kapan Pilih Mana?
| Aspek | Neo4j (Graph) | MySQL/PG (Relational) |
|---|---|---|
| Hubungan kompleks | ✅ Sangat cepat | ❌ JOIN mahal untuk deep traversal |
| Data terstruktur tabular | ❌ Overhead | ✅ Optimal |
| ACID transactions | ✅ Didukung (sejak 3.5+) | ✅ Penuh |
| Schema flexibility | ✅ Schema-free | ❌ Schema rigid |
| Shortest path | ✅ Built-in | ❌ Sangat kompleks |
| Reporting/Aggregasi | ⚠️ Biasa | ✅ Optimasi baik |
| Ekosistem tools | ⚠️ Lebih kecil | ✅ Sangat besar |
| Skalabilitas horizontal | ⚠️ Causal clustering | ✅ Sharding mature |
- Buat index pada property yang sering di-MATCH —
CREATE INDEX FOR (p:Person) ON (p.name) - Hindari Cartesian product — pastikan setiap MATCH terhubung
- Gunakan parameterized queries —
WHERE p.name = $name - Batasi depth traversal —
[:KNOWS*1..3]bukan[:KNOWS*] - Monitor query — gunakan
:PROFILEatau:EXPLAINdi Neo4j Browser - Hindari supernodes — node dengan ratusan ribu relationship
- Gunakan MERGE bukan CREATE untuk data idempotent
- Batch operations — gunakan
UNWINDuntuk bulk create
9. Quiz Pemahaman
Uji pemahaman Anda tentang Neo4j dan Cypher!
1. Apa itu "node" dalam graph database?
2. Dalam Cypher, apa arti (p:Person)-[:KNOWS]->(f)?
3. Mengapa DELETE node gagal di Neo4j?
4. Apa keunggulan utama graph database dibanding relational untuk social network?
5. Fungsi apa yang digunakan untuk mengumpulkan hasil ke list dalam Cypher?
Rangkuman
- Graph Database — menyimpan data sebagai node dan relationship, bukan tabel
- Cypher — bahasa query yang intuitif dengan pola ASCII art
(a)-[:KNOWS]->(b) - Pattern Matching — kekuatan utama: temukan pola hubungan kompleks dengan mudah
- Variable Length Path —
[:KNOWS*1..3]untuk traversal multi-hop - MERGE — create-if-not-exists untuk data idempotent
- Best untuk — social network, recommendation, fraud detection, knowledge graph
- Hindari untuk — reporting heavy, data tabular sederhana, aggregasi besar-besaran