Query the database

Once you have connected to the database, you can run queries using Cypher and the method Driver.execute_query().

Driver.execute_query() was introduced with the version 5.8 of the driver.
For queries with earlier versions, use sessions and transactions.

Write to the database

To create a node representing a person named Alice, use the Cypher clause CREATE:

Create a node representing a person named Alice
summary = driver.execute_query(
    "CREATE (:Person {name: $name})",  (1)
    name="Alice",  (2)
    database_="neo4j",  (3)
).summary
print("Created {nodes_created} nodes in {time} ms.".format(
    nodes_created=summary.counters.nodes_created,
    time=summary.result_available_after
))
1 The Cypher query
2 A map of query parameters
3 Which database the query should be run against

Read from the database

To retrieve information from the database, use the Cypher clause MATCH:

Retrieve all Person nodes
records, summary, keys = driver.execute_query(
    "MATCH (p:Person) RETURN p.name AS name",
    database_="neo4j",
)

# Loop through results and do something with them
for record in records:  (1)
    print(record.data())  # obtain record as dict

# Summary information  (2)
print("The query `{query}` returned {records_count} records in {time} ms.".format(
    query=summary.query, records_count=len(records),
    time=summary.result_available_after
))
1 records contains the result as an array of Record objects
2 summary contains the summary of execution returned by the server

Update the database

To update a node’s information in the database, use the Cypher clauses MATCH and SET:

Update node Alice to add an age property
records, summary, keys = driver.execute_query("""
    MATCH (p:Person {name: $name})
    SET p.age = $age
    """, name="Alice", age=42,
    database_="neo4j",
)
print(f"Query counters: {summary.counters}.")

To create a new relationship, linking it to two already existing node, use a combination of the Cypher clauses MATCH and CREATE:

Create a relationship :KNOWS between Alice and Bob
records, summary, keys = driver.execute_query("""
    MATCH (alice:Person {name: $name})  (1)
    MATCH (bob:Person {name: $friend})  (2)
    CREATE (alice)-[:KNOWS]->(bob)  (3)
    """, name="Alice", friend="Bob",
    database_="neo4j",
)
print(f"Query counters: {summary.counters}.")
1 Retrieve the person node named Alice and bind it to a variable alice
2 Retrieve the person node named Bob and bind it to a variable bob
3 Create a new :KNOWS relationship outgoing from the node bound to alice and attach to it the Person node named Bob

Delete from the database

To remove a node and any relationship attached to it, use the Cypher clause DETACH DELETE:

Remove the Alice node
records, summary, keys = driver.execute_query("""
    MATCH (p:Person {name: $name})
    DETACH DELETE p
    """, name="Alice",
    database_="neo4j",
)
print(f"Query counters: {summary.counters}.")

Query parameters

Do not hardcode or concatenate parameters directly into queries. Instead, always use placeholders and specify the Cypher parameters, as shown in the previous examples. This is for:

  1. performance benefits: Neo4j compiles and caches queries, but can only do so if the query structure is unchanged;

  2. security reasons: see protecting against Cypher injection.

Query parameters can be passed either as several keyword arguments, or grouped together in a dictionary as value to the parameters_ keyword argument. In case of mix, keyword-argument parameters take precedence over dictionary ones.

Pass query parameters as keyword arguments
driver.execute_query(
    "MERGE (:Person {name: $name})",
    name="Alice", age=42,
    database_="neo4j",
)
Pass query parameters in a dictionary
parameters = {
    "name": "Alice",
    "age": 42
}
driver.execute_query(
    "MERGE (:Person {name: $name})",
    parameters_=parameters,
    database_="neo4j",
)

None of your keyword query parameters may end with a single underscore. This is to avoid collisions with the keyword configuration parameters. If you need to use such parameter names, pass them in the parameters_ dictionary.

There can be circumstances where your query structure prevents the usage of parameters in all its parts. For those rare use cases, see Dynamic values in property keys, relationship types, and labels.

Error handling

Because .execute_query() can potentially raise a number of different exceptions, the best way to handle errors is to catch all exceptions in a single try/except block:

try:
    driver.execute_query(...)
except Exception as e:
    ...  # handle exception
The driver automatically retries to run a failed query, if the failure is deemed to be transient (for example due to temporary server unavailability). An exception will be raised if the operation keeps failing after a number of attempts.

Query configuration

You can supply further keyword arguments to alter the default behavior of .execute_query(). Configuration parameters are suffixed with _.

Database selection

It is recommended to always specify the database explicitly with the database_ parameter, even on single-database instances. This allows the driver to work more efficiently, as it saves a network round-trip to the server to resolve the home database. If no database is given, the user’s home database is used.

driver.execute_query(
    "MATCH (p:Person) RETURN p.name",
    database_="neo4j",
)
Specifying the database through the configuration method is preferred over the USE Cypher clause. If the server runs on a cluster, queries with USE require server-side routing to be enabled. Queries may also take longer to execute as they may not reach the right cluster member at the first attempt, and need to be routed to one containing the requested database.

Request routing

In a cluster environment, all queries are directed to the leader node by default. To improve performance on read queries, you can use the argument routing_="r" to route a query to the read nodes.

driver.execute_query(
    "MATCH (p:Person) RETURN p.name",
    routing_="r",  # short for neo4j.RoutingControl.READ
    database_="neo4j",
)

Although executing a write query in read mode likely results in a runtime error, you should not rely on this for access control. The difference between the two modes is that read transactions will be routed to any node of a cluster, whereas write ones will be directed to the leader. In other words, there is no guarantee that a write query submitted in read mode will be rejected.

Run queries as a different user

You can execute a query through a different user with the parameter auth_. Switching user at the query level is cheaper than creating a new Driver object. The query is then run within the security context of the given user (i.e., home database, permissions, etc.).
Query-scoped authentication requires a server version >= 5.8.

driver.execute_query(
    "MATCH (p:Person) RETURN p.name",
    auth_=("somebody_else", "their_password"),
    database_="neo4j",
)

The parameter impersonated_user_ provides a similar functionality, and is available in driver/server versions >= 4.4. The difference is that you don’t need to know a user’s password to impersonate them, but the user under which the Driver was created needs to have the appropriate permissions.

driver.execute_query(
    "MATCH (p:Person) RETURN p.name",
    impersonated_user_="somebody_else",
    database_="neo4j",
)

Transform query result

You can transform a query’s result into a different data structure using the result_transformer_ argument. The driver provides built-in methods to transform the result into a pandas dataframe or into a graph, but you can also craft your own transformer.

For more information, see Manipulate query results.

A full example

from neo4j import GraphDatabase


URI = "<URI for Neo4j database>"
AUTH = ("<Username>", "<Password>")

people = [{"name": "Alice", "age": 42, "friends": ["Bob", "Peter", "Anna"]},
          {"name": "Bob", "age": 19},
          {"name": "Peter", "age": 50},
          {"name": "Anna", "age": 30}]

with GraphDatabase.driver(URI, auth=AUTH) as driver:
    try:
        # Create some nodes
        for person in people:
            records, summary, keys = driver.execute_query(
                "MERGE (p:Person {name: $person.name, age: $person.age})",
                person=person,
                database_="neo4j",
            )

        # Create some relationships
        for person in people:
            if person.get("friends"):
                records, summary, keys = driver.execute_query("""
                    MATCH (p:Person {name: $person.name})
                    UNWIND $person.friends AS friend_name
                    MATCH (friend:Person {name: friend_name})
                    MERGE (p)-[:KNOWS]->(friend)
                    """, person=person,
                    database_="neo4j",
                )

        # Retrieve Alice's friends who are under 40
        records, summary, keys = driver.execute_query("""
            MATCH (p:Person {name: $name})-[:KNOWS]-(friend:Person)
            WHERE friend.age < $age
            RETURN friend
            """, name="Alice", age=40,
            routing_="r",
            database_="neo4j",
        )
        # Loop through results and do something with them
        for record in records:
            print(record)
        # Summary information
        print("The query `{query}` returned {records_count} records in {time} ms.".format(
            query=summary.query, records_count=len(records),
            time=summary.result_available_after
        ))

    except Exception as e:
        print(e)
        # further logging/processing

Glossary

LTS

A Long Term Support release is one guaranteed to be supported for a number of years. Neo4j 4.4 is LTS, and Neo4j 5 will also have an LTS version.

Aura

Aura is Neo4j’s fully managed cloud service. It comes with both free and paid plans.

Cypher

Cypher is Neo4j’s graph query language that lets you retrieve data from the database. It is like SQL, but for graphs.

APOC

Awesome Procedures On Cypher (APOC) is a library of (many) functions that can not be easily expressed in Cypher itself.

Bolt

Bolt is the protocol used for interaction between Neo4j instances and drivers. It listens on port 7687 by default.

ACID

Atomicity, Consistency, Isolation, Durability (ACID) are properties guaranteeing that database transactions are processed reliably. An ACID-compliant DBMS ensures that the data in the database remains accurate and consistent despite failures.

eventual consistency

A database is eventually consistent if it provides the guarantee that all cluster members will, at some point in time, store the latest version of the data.

causal consistency

A database is causally consistent if read and write queries are seen by every member of the cluster in the same order. This is stronger than eventual consistency.

NULL

The null marker is not a type but a placeholder for absence of value. For more information, see Cypher → Working with null.

transaction

A transaction is a unit of work that is either committed in its entirety or rolled back on failure. An example is a bank transfer: it involves multiple steps, but they must all succeed or be reverted, to avoid money being subtracted from one account but not added to the other.

backpressure

Backpressure is a force opposing the flow of data. It ensures that the client is not being overwhelmed by data faster than it can handle.

transaction function

A transaction function is a callback executed by an execute_read or execute_write call. The driver automatically re-executes the callback in case of server failure.

Driver

A Driver object holds the details required to establish connections with a Neo4j database.