A Graph Database is a type of NoSQL database designed to store and query highly interconnected data using nodes (entities) and edges (relationships).
Instead of using tables or documents, graph databases model data as a graph — ideal for scenarios where relationships are as important as the data itself.
🧱 Basic Components
Term | Description | Example |
|---|---|---|
Node | Represents an entity |
|
Edge | Represents a relationship between nodes |
|
Property | Key-value data stored on nodes/edges |
|
🧠 Why Use Graph Databases?
They allow fast traversal of deeply nested relationships without complex joins. Ideal for:
Exploring relationships
Multi-hop queries
Pattern matching
🔍 Use Cases
Use Case | Description |
|---|---|
🧑🤝🧑 Social Networks | Users connected by friendships, likes, followers |
🛍️ Recommendation Engines | "People who liked X also liked Y" |
🧠 Knowledge Graphs | Semantic relationships between concepts |
🚨 Fraud Detection | Identify patterns of suspicious transactions |
📍 Geospatial / Route Optimization | Find shortest paths between places |
🧰 Popular Graph Databases
Database | Features |
|---|---|
Neo4j | Most widely used; Cypher query language |
ArangoDB | Multi-model DB (graph + document) |
OrientDB | Supports documents and graphs |
Amazon Neptune | Fully managed graph DB on AWS |
JanusGraph | Open-source, scalable over big data backends (Cassandra, HBase) |
🧾 Query Example (Neo4j – Cypher Syntax)
MATCH (u:User)-[:FRIENDS_WITH]->(f:User)
WHERE u.name = "Alice"
RETURN f.name➡️ This returns the names of all users who are friends with Alice.
✅ Advantages
Advantage | Description |
|---|---|
🔍 Fast relationship queries | Traverses deep connections efficiently |
🔁 No need for joins | All relationships are first-class citizens |
🧱 Flexible schema | Nodes can have any shape or properties |
📊 Perfect for networked data | Easy to model complex webs of connections |
❌ Limitations
Limitation | Description |
|---|---|
❌ Not ideal for flat data | Overkill for simple key-value or tabular use cases |
❌ Scalability complexity | May require tuning and advanced setup for large graphs |
❌ Fewer options | Fewer tools and community support compared to SQL/Document DBs |
🧮 Graph vs Other NoSQL Types
Feature | Graph DBs | Other NoSQL Types |
|---|---|---|
Primary Data Model | Nodes & Edges | Key-Value, JSON, Columns |
Best for | Relationships & traversals | Documents, analytics, caching |
Query Type | Pattern matching, traversal | Key/ID-based retrieval |
Join Support | Built-in & optimized | Usually not supported |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Graph Databases — Try It Yourself</title>
<style>
.infographic-graphdb {
--ink: #1a1a1a;
--ink-soft: #5c5c5c;
--paper: #ffffff;
--paper-alt: #f0ece4;
--accent: #2563eb;
--accent-soft:#e3ebfd;
--line: #ddd6c9;
--good: #16a34a;
--good-soft: #dcfce7;
--on-accent: #ffffff;
--display-font: Georgia, 'Iowan Old Style', serif;
--body-font: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
--mono-font: 'SF Mono', Consolas, monospace;
background: var(--paper);
color: var(--ink);
font-family: var(--body-font);
max-width: 960px;
margin: 0 auto;
box-sizing: border-box;
overflow-x: hidden;
}
.infographic-graphdb[data-theme="dark"] {
--ink: #e8e6e1;
--ink-soft: #9a968c;
--paper: #121212;
--paper-alt: #1e1e26;
--accent: #5b8def;
--accent-soft:#202942;
--line: #2c313d;
--good: #4ade80;
--good-soft: #123420;
--on-accent: #101418;
}
.infographic-graphdb * { box-sizing: border-box; }
.gd-head { text-align: center; margin-bottom: 0.25rem; }
.gd-head h2 {
font-family: var(--display-font);
font-size: 1.95rem;
margin: 0 0 0.5rem;
}
.gd-head p {
color: var(--ink-soft);
font-size: 1rem;
margin: 0 auto;
max-width: 620px;
line-height: 1.55;
}
.gd-frame {
background: var(--paper-alt);
border: 1px solid var(--line);
border-radius: 10px;
padding: 1.25rem;
margin-top: 1.75rem;
}
.gd-query-line {
font-family: var(--mono-font);
font-size: 0.85rem;
text-align: center;
background: var(--paper);
border: 1px solid var(--line);
border-radius: 8px;
padding: 0.65rem 1rem;
margin-bottom: 1.1rem;
}
.gd-hop-row { display: flex; justify-content: center; gap: 0.6rem; margin-bottom: 1.25rem; flex-wrap: wrap; }
.gd-hop-btn {
font: inherit;
font-size: 0.85rem;
background: var(--paper);
border: 1px solid var(--line);
border-radius: 999px;
padding: 0.5rem 1.05rem;
cursor: pointer;
color: var(--ink);
}
.gd-hop-btn:hover { border-color: var(--accent); }
.gd-hop-btn.is-active { border-color: var(--accent); background: var(--accent-soft); color: var(--accent); font-weight: 600; }
.gd-race { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; min-width: 0; }
@media (max-width: 700px) { .gd-race { grid-template-columns: 1fr; } }
.gd-race-col { min-width: 0; }
.gd-race-title { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--ink-soft); margin: 0 0 0.6rem; text-align: center; }
/* graph visual */
.gd-graph-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.gd-graph-scroll svg { width: 100%; height: auto; display: block; min-width: 280px; }
.gd-node-circle { fill: var(--paper); stroke: var(--accent); stroke-width: 2; transition: fill 0.25s, stroke-width 0.25s; }
.gd-node-circle.is-active { fill: var(--accent); stroke-width: 3; }
.gd-node-text { font-size: 12px; font-weight: 700; font-family: var(--body-font); text-anchor: middle; fill: var(--ink); transition: fill 0.25s; pointer-events: none; }
.gd-node-text.is-active { fill: var(--on-accent); }
.gd-edge { stroke: var(--line); stroke-width: 2; fill: none; transition: stroke 0.25s, stroke-width 0.25s; }
.gd-edge.is-active { stroke: var(--accent); stroke-width: 3; }
/* rdbms visual */
.dc-table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; min-width: 0; margin-bottom: 0.6rem; }
table.gd-table {
width: 100%;
min-width: 200px;
border-collapse: collapse;
background: var(--paper);
border-radius: 7px;
font-size: 0.78rem;
}
table.gd-table th, table.gd-table td {
border: 1px solid var(--line);
padding: 0.35rem 0.5rem;
text-align: left;
white-space: nowrap;
transition: background 0.25s;
}
table.gd-table th { background: var(--accent); color: var(--on-accent); font-weight: 700; font-size: 0.68rem; }
table.gd-table tr.is-checking td { background: var(--accent-soft); }
table.gd-table tr.is-match td { background: var(--good-soft); }
table.gd-table tr.is-skip td { opacity: 0.4; }
.gd-table-label { font-size: 0.72rem; color: var(--ink-soft); margin: 0 0 0.3rem; }
.gd-run-btn {
display: block;
margin: 1.25rem auto 0;
font: inherit;
font-size: 0.88rem;
background: var(--accent);
color: var(--on-accent);
border: none;
border-radius: 7px;
padding: 0.6rem 1.3rem;
cursor: pointer;
}
.gd-run-btn:hover { opacity: 0.9; }
.gd-run-btn:disabled { opacity: 0.4; cursor: not-allowed; }
.gd-log-scroll { overflow-x: auto; margin-top: 1.1rem; }
.gd-log {
background: var(--ink);
color: #d4e2ff;
border-radius: 8px;
padding: 0.75rem 0.9rem;
font-family: var(--mono-font);
font-size: 0.76rem;
line-height: 1.6;
min-height: 2rem;
}
.infographic-graphdb[data-theme="dark"] .gd-log { background: #05070a; }
.gd-log-line { opacity: 0; animation: gd-line-in 0.25s ease forwards; }
@keyframes gd-line-in { to { opacity: 1; } }
.gd-log-placeholder { color: #7c8aa8; font-style: italic; }
.gd-scoreboard { display: flex; gap: 1rem; justify-content: center; margin-top: 1.25rem; flex-wrap: wrap; }
.gd-score { background: var(--paper); border: 1px solid var(--line); border-radius: 8px; padding: 0.6rem 1.1rem; text-align: center; min-width: 190px; }
.gd-score.is-winner { border-color: var(--good); }
.gd-score-label { font-size: 0.72rem; color: var(--ink-soft); text-transform: uppercase; letter-spacing: 0.04em; }
.gd-score-value { font-family: var(--mono-font); font-size: 1rem; margin-top: 0.15rem; color: var(--accent); }
.gd-score.is-winner .gd-score-value { color: var(--good); }
.gd-reset-btn {
display: block;
margin: 1rem auto 0;
font: inherit;
font-size: 0.8rem;
background: var(--paper);
border: 1px solid var(--line);
border-radius: 7px;
padding: 0.4rem 0.9rem;
cursor: pointer;
color: var(--ink);
}
.gd-reset-btn:hover { border-color: var(--accent); }
@media (prefers-reduced-motion: reduce) {
.infographic-graphdb *, .infographic-graphdb *::before, .infographic-graphdb *::after {
transition-duration: 0.001ms !important;
animation-duration: 0.001ms !important;
}
}
</style>
</head>
<body>
<div class="infographic-graphdb" id="graphdb">
<div class="gd-head">
<h2>Graph Databases — Try It Yourself</h2>
<p>Same social network, two ways to store it. Walk relationships as edges, or reconstruct them through a join table — and watch the cost per extra hop.</p>
</div>
<div class="gd-frame">
<p class="gd-query-line" id="gdQuery">MATCH (Alice)-[:FRIENDS_WITH]->(f) RETURN f.name</p>
<div class="gd-hop-row">
<button class="gd-hop-btn is-active" data-hops="1" type="button">1-hop: Friends of Alice</button>
<button class="gd-hop-btn" data-hops="2" type="button">2-hop: Friends of friends</button>
</div>
<div class="gd-race">
<div class="gd-race-col">
<p class="gd-race-title">Graph Database</p>
<div class="gd-graph-scroll">
<svg viewBox="0 0 480 260" role="img" aria-label="Social graph of Alice, Bob, Carol, David, Emma">
<defs>
<marker id="gdArrow" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto">
<path d="M0,0 L8,4 L0,8 z" fill="var(--line)"></path>
</marker>
<marker id="gdArrowActive" markerWidth="8" markerHeight="8" refX="4" refY="4" orient="auto">
<path d="M0,0 L8,4 L0,8 z" fill="var(--accent)"></path>
</marker>
</defs>
<path class="gd-edge" id="edge-A-B" d="M130,120 L250,70" marker-end="url(#gdArrow)"></path>
<path class="gd-edge" id="edge-A-C" d="M130,150 L250,190" marker-end="url(#gdArrow)"></path>
<path class="gd-edge" id="edge-B-D" d="M310,65 L410,55" marker-end="url(#gdArrow)"></path>
<path class="gd-edge" id="edge-C-E" d="M310,195 L410,205" marker-end="url(#gdArrow)"></path>
<circle class="gd-node-circle" id="node-A" cx="100" cy="135" r="32"></circle>
<text class="gd-node-text" id="text-A" x="100" y="140">Alice</text>
<circle class="gd-node-circle" id="node-B" cx="280" cy="60" r="30"></circle>
<text class="gd-node-text" id="text-B" x="280" y="65">Bob</text>
<circle class="gd-node-circle" id="node-C" cx="280" cy="200" r="30"></circle>
<text class="gd-node-text" id="text-C" x="280" y="205">Carol</text>
<circle class="gd-node-circle" id="node-D" cx="440" cy="45" r="28"></circle>
<text class="gd-node-text" id="text-D" x="440" y="50">David</text>
<circle class="gd-node-circle" id="node-E" cx="440" cy="215" r="28"></circle>
<text class="gd-node-text" id="text-E" x="440" y="220">Emma</text>
</svg>
</div>
</div>
<div class="gd-race-col">
<p class="gd-race-title">RDBMS (join table)</p>
<p class="gd-table-label">Users</p>
<div class="dc-table-scroll">
<table class="gd-table" id="gdUsersTable">
<thead><tr><th>ID</th><th>Name</th></tr></thead>
<tbody>
<tr data-id="A"><td>A</td><td>Alice</td></tr>
<tr data-id="B"><td>B</td><td>Bob</td></tr>
<tr data-id="C"><td>C</td><td>Carol</td></tr>
<tr data-id="D"><td>D</td><td>David</td></tr>
<tr data-id="E"><td>E</td><td>Emma</td></tr>
</tbody>
</table>
</div>
<p class="gd-table-label">Friendships</p>
<div class="dc-table-scroll">
<table class="gd-table" id="gdFriendTable">
<thead><tr><th>UserID</th><th>FriendID</th></tr></thead>
<tbody>
<tr data-from="A" data-to="B"><td>A</td><td>B</td></tr>
<tr data-from="A" data-to="C"><td>A</td><td>C</td></tr>
<tr data-from="B" data-to="D"><td>B</td><td>D</td></tr>
<tr data-from="C" data-to="E"><td>C</td><td>E</td></tr>
</tbody>
</table>
</div>
</div>
</div>
<button class="gd-run-btn" id="gdRunBtn" type="button">▶ Run the query</button>
<div class="gd-log-scroll">
<div class="gd-log" id="gdLog"><span class="gd-log-placeholder">Pick a hop count, then run the query...</span></div>
</div>
<div class="gd-scoreboard">
<div class="gd-score" id="gdScoreGraph"><div class="gd-score-label">Graph traversal</div><div class="gd-score-value">–</div></div>
<div class="gd-score" id="gdScoreRdbms"><div class="gd-score-label">RDBMS joins</div><div class="gd-score-value">–</div></div>
</div>
<button class="gd-reset-btn" id="gdReset" type="button">Reset</button>
</div>
</div>
<script>
(function () {
const wrapper = document.getElementById('graphdb');
const bodyAttr = document.body.getAttribute('data-darkmode');
let dark;
if (bodyAttr === 'true') {
dark = true;
} else if (bodyAttr === 'false') {
dark = false;
} else {
const match = document.cookie.match(/(?:^|;\s*)darkMode=([^;]+)/);
dark = match ? match[1] === 'true' : false;
}
if (dark) wrapper.setAttribute('data-theme', 'dark');
})();
(function () {
const names = { A: 'Alice', B: 'Bob', C: 'Carol', D: 'David', E: 'Emma' };
const edges = [['A', 'B'], ['A', 'C'], ['B', 'D'], ['C', 'E']];
const hopBtns = document.querySelectorAll('.gd-hop-btn');
const queryLine = document.getElementById('gdQuery');
const runBtn = document.getElementById('gdRunBtn');
const log = document.getElementById('gdLog');
const friendTable = document.getElementById('gdFriendTable');
const usersTable = document.getElementById('gdUsersTable');
const scoreGraph = document.querySelector('#gdScoreGraph .gd-score-value');
const scoreRdbms = document.querySelector('#gdScoreRdbms .gd-score-value');
const scoreGraphCard = document.getElementById('gdScoreGraph');
const scoreRdbmsCard = document.getElementById('gdScoreRdbms');
let hops = 1;
hopBtns.forEach(btn => {
btn.addEventListener('click', () => {
hops = Number(btn.dataset.hops);
hopBtns.forEach(b => b.classList.remove('is-active'));
btn.classList.add('is-active');
queryLine.textContent = hops === 1
? 'MATCH (Alice)-[:FRIENDS_WITH]->(f) RETURN f.name'
: 'MATCH (Alice)-[:FRIENDS_WITH]->()-[:FRIENDS_WITH]->(fof) RETURN fof.name';
clearAll();
});
});
function clearLog() { log.innerHTML = ''; }
function appendLog(text) {
if (log.querySelector('.gd-log-placeholder')) log.innerHTML = '';
const line = document.createElement('div');
line.className = 'gd-log-line';
line.textContent = text;
log.appendChild(line);
}
function clearAll() {
document.querySelectorAll('.gd-node-circle, .gd-node-text').forEach(el => el.classList.remove('is-active'));
document.querySelectorAll('.gd-edge').forEach(el => { el.classList.remove('is-active'); el.setAttribute('marker-end', 'url(#gdArrow)'); });
friendTable.querySelectorAll('tr').forEach(tr => tr.classList.remove('is-checking', 'is-match', 'is-skip'));
usersTable.querySelectorAll('tr').forEach(tr => tr.classList.remove('is-checking', 'is-match'));
}
function activateNode(id) {
document.getElementById('node-' + id).classList.add('is-active');
document.getElementById('text-' + id).classList.add('is-active');
}
function activateEdge(from, to) {
const el = document.getElementById('edge-' + from + '-' + to);
el.classList.add('is-active');
el.setAttribute('marker-end', 'url(#gdArrowActive)');
}
function delay(ms) { return new Promise(res => setTimeout(res, ms)); }
async function runGraphSide() {
activateNode('A');
appendLog('[Graph] Start at Alice.');
await delay(350);
const hop1 = edges.filter(e => e[0] === 'A');
for (const [from, to] of hop1) {
activateEdge(from, to);
activateNode(to);
appendLog('[Graph] Walk edge ' + names[from] + ' → ' + names[to] + '.');
await delay(350);
}
let edgeCount = hop1.length;
if (hops === 2) {
const frontier = hop1.map(e => e[1]);
for (const mid of frontier) {
const hop2 = edges.filter(e => e[0] === mid);
for (const [from, to] of hop2) {
activateEdge(from, to);
activateNode(to);
appendLog('[Graph] Walk edge ' + names[from] + ' → ' + names[to] + '.');
await delay(350);
edgeCount++;
}
}
}
scoreGraph.textContent = edgeCount + ' edge' + (edgeCount === 1 ? '' : 's') + ' walked directly';
}
async function runRdbmsSide() {
const userRow = usersTable.querySelector('tr[data-id="A"]');
userRow.classList.add('is-checking');
appendLog('[RDBMS] Look up Users row: Alice.');
await delay(350);
userRow.classList.remove('is-checking');
userRow.classList.add('is-match');
let scanned = 0;
let lookups = 1;
async function scanFor(userId, label) {
const results = [];
const rows = Array.from(friendTable.querySelectorAll('tbody tr'));
appendLog('[RDBMS] Scan Friendships table for UserID = ' + userId + ' (' + label + ').');
for (const row of rows) {
row.classList.remove('is-checking', 'is-match', 'is-skip');
row.classList.add('is-checking');
scanned++;
const isMatch = row.dataset.from === userId;
await delay(220);
row.classList.remove('is-checking');
row.classList.add(isMatch ? 'is-match' : 'is-skip');
if (isMatch) results.push(row.dataset.to);
}
for (const r of results) {
const uRow = usersTable.querySelector('tr[data-id="' + r + '"]');
uRow.classList.add('is-match');
lookups++;
appendLog('[RDBMS] Look up Users row for FriendID ' + r + ' → ' + names[r] + '.');
await delay(200);
}
return results;
}
const hop1Results = await scanFor('A', "Alice's friends");
if (hops === 2) {
for (const mid of hop1Results) {
await scanFor(mid, names[mid] + "'s friends");
}
}
scoreRdbms.textContent = lookups + ' row lookups + ' + scanned + ' rows scanned';
}
runBtn.addEventListener('click', async () => {
clearAll();
clearLog();
runBtn.disabled = true;
scoreGraphCard.classList.remove('is-winner');
scoreRdbmsCard.classList.remove('is-winner');
await runGraphSide();
await runRdbmsSide();
scoreGraphCard.classList.add('is-winner');
runBtn.disabled = false;
});
document.getElementById('gdReset').addEventListener('click', () => {
clearAll();
clearLog();
log.innerHTML = '<span class="gd-log-placeholder">Pick a hop count, then run the query...</span>';
scoreGraph.textContent = '–';
scoreRdbms.textContent = '–';
scoreGraphCard.classList.remove('is-winner');
scoreRdbmsCard.classList.remove('is-winner');
runBtn.disabled = false;
});
})();
</script>
</body>
</html>.demo {
font-family: system-ui, sans-serif;
padding: 16px;
}
.demo h1 {
color: #2563eb;
margin: 0 0 8px;
}
.demo p {
color: #4b5563;
margin: 0;
}document.querySelector('.demo h1')?.addEventListener('click', () => {
alert('Hello from live preview!');
});