⏳
Loading cheatsheet...
Graph Concepts, Cypher Basics, Advanced Queries, Indexes, Procedures, Drivers — graph database mastery.
Neo4j is a native graph database that stores data as nodes, relationships, and properties. It uses the Cypher query language for pattern-matching queries.
| Concept | Description |
|---|---|
| Node | An entity (person, place, thing) with properties and labels |
| Relationship | A directed connection between two nodes with a type |
| Property | Key-value pair stored on nodes or relationships |
| Label | Categories/tags for nodes (e.g., :Person, :Movie) |
| Path | An alternating sequence of nodes and relationships |
| Graph | Collection of nodes and relationships |
-- Nodes: () with labels
(n:Person)
(p:Person {name: "Alice"})
-- Relationships: -[]-> with types
(n)-[:KNOWS]->(m)
(n)-[:KNOWS {since: 2020}]->(m)
-- Properties in {}
{key: "value", age: 30}
-- Pattern in MATCH
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE m.title = "The Matrix"
RETURN p, m# ── Docker (recommended) ──
docker run -d \
--name neo4j \
-p 7474:7474 \
-p 7687:7687 \
-e NEO4J_AUTH=neo4j/password123 \
neo4j:5-community
# ── Neo4j Desktop ──
# Download from https://neo4j.com/download/
# ── Connect with cypher-shell ──
cypher-shell -a bolt://localhost:7687 -u neo4j -p password123
# ── Python driver install ──
pip install neo4j-- Create single node
CREATE (n:Person {name: "Alice", age: 30})
-- Create multiple nodes
CREATE
(alice:Person {name: "Alice", age: 30}),
(bob:Person {name: "Bob", age: 25}),
(charlie:Person {name: "Charlie", age: 35})
-- Create node with relationship
CREATE (alice:Person {name: "Alice"})-[:KNOWS {since: 2020}]->(bob:Person {name: "Bob"})
-- Create with multiple labels
CREATE (p:Person:Employee {name: "Alice", empId: 1001})
-- Create and return
CREATE (n:Product {name: "Widget", price: 9.99}) RETURN n-- Find all nodes with a label
MATCH (n:Person) RETURN n
-- Find by property
MATCH (p:Person {name: "Alice"}) RETURN p
-- Find with WHERE clause
MATCH (p:Person)
WHERE p.age > 25 AND p.name STARTS WITH "A"
RETURN p.name, p.age
-- Pattern matching with relationships
MATCH (p:Person)-[:KNOWS]->(friend:Person)
RETURN p.name, collect(friend.name) AS friends
-- Find people Alice knows who are over 25
MATCH (alice:Person {name: "Alice"})-[:KNOWS]->(friend:Person)
WHERE friend.age > 25
RETURN friend.name
-- Multiple relationship types
MATCH (p:Person)-[:KNOWS|WORKS_WITH]->(other:Person)
RETURN p, other-- SET: update properties
MATCH (p:Person {name: "Alice"})
SET p.age = 31, p.email = "alice@example.com"
-- SET: add label
MATCH (p:Person {name: "Alice"})
SET p:Manager
-- SET: set all properties from map
MATCH (p:Person {name: "Alice"})
SET p += {age: 31, city: "NYC"}
-- REMOVE: remove a property
MATCH (p:Person {name: "Alice"})
REMOVE p.email
-- REMOVE: remove a label
MATCH (p:Person {name: "Alice"})
REMOVE p:Manager
-- DELETE: remove relationship/node
MATCH (p:Person {name: "Bob"})
DETACH DELETE p
-- Delete all nodes and relationships
MATCH (n) DETACH DELETE n-- MERGE: create if not exists, match if exists
MERGE (p:Person {name: "Alice"})
ON CREATE SET p.age = 30, p.createdAt = datetime()
ON MATCH SET p.lastSeen = datetime()
RETURN p
-- MERGE with relationship (ensures no duplicates)
MERGE (alice:Person {name: "Alice"})
MERGE (bob:Person {name: "Bob"})
MERGE (alice)-[:KNOWS]->(bob)
RETURN alice, bob-- COUNT, SUM, AVG, MIN, MAX
MATCH (p:Person)
RETURN count(p) AS totalPeople,
avg(p.age) AS avgAge,
min(p.age) AS youngest,
max(p.age) AS oldest
-- GROUP BY with collect()
MATCH (p:Person)-[:KNOWS]->(friend:Person)
RETURN p.name, count(friend) AS friendCount,
collect(friend.name) AS friends
ORDER BY friendCount DESC
-- ORDER BY, SKIP, LIMIT (pagination)
MATCH (p:Person)
RETURN p.name, p.age
ORDER BY p.age DESC
SKIP 10 LIMIT 5
-- DISTINCT
MATCH (p:Person)-[:KNOWS]->(:Person)
RETURN DISTINCT p.name AS knowsSomeone
-- WITH (pipeline results)
MATCH (p:Person)-[:KNOWS]->(friend:Person)
WITH p, count(friend) AS fc
WHERE fc > 2
RETURN p.name AS popularPerson, fc AS friendCount-- Variable-length: 1 to 3 hops
MATCH path = (alice:Person {name: "Alice"})-[:KNOWS*1..3]->(fof:Person)
RETURN path
-- Any depth (use with caution)
MATCH path = (a:Person)-[:KNOWS*]->(b:Person)
WHERE a.name <> b.name
RETURN path
-- Shortest path
MATCH p = shortestPath(
(alice:Person {name: "Alice"})-[:KNOWS*]-(bob:Person {name: "Bob"})
)
RETURN p
-- All shortest paths
MATCH p = allShortestPaths(
(a:Person {name: "Alice"})-[:KNOWS*]-(b:Person {name: "Bob"})
)
RETURN p
-- OPTIONAL MATCH (left outer join)
MATCH (p:Person {name: "Alice"})
OPTIONAL MATCH (p)-[:WORKS_AT]->(c:Company)
RETURN p.name, c.name AS company
-- List comprehension
MATCH (p:Person)-[:KNOWS]->(friend:Person)
RETURN p.name,
[f IN collect(friend) WHERE f.age > 25 | f.name] AS olderFriends-- CASE expression
MATCH (p:Person)
RETURN p.name,
CASE
WHEN p.age < 18 THEN "Minor"
WHEN p.age < 65 THEN "Adult"
ELSE "Senior"
END AS category
-- UNION (combine results)
MATCH (p:Person)-[:KNOWS]->(f:Person) RETURN p.name, f.name
UNION
MATCH (p:Person)-[:WORKS_WITH]->(c:Person) RETURN p.name, c.name
-- Pattern comprehension
MATCH (p:Person)
RETURN p.name,
[(p)-[:KNOWS]->(f:Person) | f.name] AS friends-- ── Create Indexes ──
CREATE INDEX person_name_idx FOR (p:Person) ON (p.name);
-- Composite index
CREATE INDEX person_name_age_idx FOR (p:Person) ON (p.name, p.age);
-- Full-text index
CREATE FULLTEXT INDEX movieTitleIdx
FOR (m:Movie) ON EACH [m.title, m.description];
-- Show indexes
SHOW INDEXES;
-- Drop index
DROP INDEX person_name_idx;
-- ── Constraints (unique + index) ──
CREATE CONSTRAINT person_name_unique
FOR (p:Person) REQUIRE p.name IS UNIQUE;
-- Existence constraint (Neo4j 5+)
CREATE CONSTRAINT person_name_exists
FOR (p:Person) REQUIRE p.name IS NOT NULL;
-- Key constraint (composite unique)
CREATE CONSTRAINT person_id_key
FOR (p:Person) REQUIRE (p.firstName, p.lastName) IS NODE KEY;
-- Show constraints
SHOW CONSTRAINTS;
-- Drop constraint
DROP CONSTRAINT person_name_unique;| Type | When to Use |
|---|---|
| Range Index | Equality, range, sort on single property |
| Composite Index | Multi-property equality queries |
| Full-text Index | CONTAINS, STARTS WITH, ENDS WITH |
| Text Index | Deprecated (use Full-text) |
| Type | Purpose |
|---|---|
| UNIQUE | Property must be unique (creates index) |
| NOT NULL | Property must exist on all nodes |
| NODE KEY | Composite unique + NOT NULL |
| Existence (rel) | Relationship must have property |
MATCH (p:Person {name: $name} benefits from a unique constraint or index on Person.name. Without an index, Neo4j performs a label scan (O(n)).| Function | Category | Example |
|---|---|---|
| size() | Collection | size([1,2,3]) → 3 |
| length() | Path/String | length(p), length("hello") |
| coalesce() | Null handling | coalesce(p.name, "Unknown") |
| range() | Generate range | range(1, 5) → [1,2,3,4,5] |
| substring() | String | substring("hello", 0, 3) → "hel" |
| toUpper() | String | toUpper("hello") → "HELLO" |
| datetime() | Temporal | datetime("2025-01-15") |
| date() | Temporal | date() → today |
| duration() | Temporal | duration({days: 7}) |
| point() | Spatial | point({x: 0, y: 0}) |
| exists() | Check property | exists(p.email) |
| type() | Relationship | type(r) → "KNOWS" |
-- Install APOC
-- For Docker: add --env NEO4J_apoc_export_file_enabled=true
-- --env NEO4J_apoc_import_file_enabled=true
-- --env NEO4J_apoc_import_file_use__neo4j__config=true
-- --env NEO4JLABS_PLUGINS='["apoc"]'
-- neo4j:5-community
-- APOC path expand (variable-length with filters)
MATCH (a:Person {name: "Alice"})
CALL apoc.path.expand(a, "KNOWS>", null, 1, 3) YIELD path
RETURN path
-- APOC do when (conditional execution)
CALL apoc.do.when(
condition,
"ON_TRUE_QUERY",
"ON_FALSE_QUERY",
params
) YIELD value RETURN value
-- APOC periodic commit (batch processing)
CALL apoc.periodic.iterate(
"MATCH (p:Person) RETURN p",
"SET p.processed = true",
{batchSize: 1000}
)
-- APOC generate (create test data)
CALL apoc.generate.utils.config()
-- APOC load JSON
CALL apoc.load.json("file:///data.json")
YIELD value
RETURN valuefrom neo4j import GraphDatabase
class Neo4jClient:
def __init__(self, uri, user, password):
self.driver = GraphDatabase.driver(uri, auth=(user, password))
def close(self):
self.driver.close()
def create_person(self, name, age):
with self.driver.session() as session:
session.run(
"CREATE (p:Person {name: $name, age: $age})",
name=name, age=age
)
def get_friends(self, name):
with self.driver.session() as session:
result = session.run("""
MATCH (p:Person {name: $name})-[:KNOWS]->(f:Person)
RETURN f.name AS friend
""", name=name)
return [r["friend"] for r in result]
def get_person_count(self):
with self.driver.session() as session:
result = session.run("MATCH (p:Person) RETURN count(p) AS cnt")
return result.single()["cnt"]
# Usage
client = Neo4jClient("bolt://localhost:7687", "neo4j", "password")
client.create_person("Alice", 30)
print(client.get_friends("Alice"))
client.close()const neo4j = require('neo4j-driver');
const driver = neo4j.driver(
'bolt://localhost:7687',
neo4j.auth.basic('neo4j', 'password')
);
async function createPerson(name, age) {
const session = driver.session();
try {
await session.run(
'CREATE (p:Person {name: $name, age: $age})',
{ name, age }
);
} finally {
await session.close();
}
}
async function getFriends(name) {
const session = driver.session();
try {
const result = await session.run(
`MATCH (p:Person {name: $name})-[:KNOWS]->(f:Person)
RETURN f.name AS friend`,
{ name }
);
return result.records.map(r => r.get('friend'));
} finally {
await session.close();
}
}
// Usage
await createPerson('Alice', 30);
const friends = await getFriends('Alice');
console.log(friends);
await driver.close();driver.session() for each unit of work, and always close sessions. Never create a new driver for each query.