Раздел 39 · Neo4j и графовые базы

Работа из приложения: драйверы Python и JS

junior~20 мин

без входаВойди, чтобы отмечать прогресс, вести заметки и решать задачи в редакторе. войти

До сих пор запросы писал человек: в cypher-shell, в Browser, в песочнице. В настоящем проекте запросы шлёт приложение: сайт клуба показывает знакомых, бот записывает оценки. Для этого у Neo4j есть официальные драйверы для Python, JavaScript, Java, Go и .NET. В этом уроке пишем по маленькому скрипту на Python и на JavaScript. Выводы ниже получены на Neo4j 2026.10.1 в Docker из урока про установку с загруженным графом клуба, драйверы neo4j 6.4 для Python и neo4j-driver 6.2 для Node.js.

Как драйвер говорит с базой

Драйвер ходит к серверу по протоколу Bolt на порт 7687, тому же, что cypher-shell. Адрес сервера пишется как neo4j://localhost:7687. Схема neo4j:// умеет работать и с одним сервером, и с кластером, поэтому её берут по умолчанию. Есть и bolt://, она подключается строго к одному серверу.

Главный объект это сам драйвер. Внутри он держит пул соединений, поэтому его создают один раз на всё приложение и закрывают при выходе, а не на каждый запрос.

Python

Ставим драйвер: pip install neo4j. Скрипт находит знакомых Ани:

from neo4j import GraphDatabase

URI = "neo4j://localhost:7687"
AUTH = ("neo4j", "secret-password")

with GraphDatabase.driver(URI, auth=AUTH) as driver:
    driver.verify_connectivity()
    records, summary, keys = driver.execute_query(
        """
        MATCH (p:Person {name: $name})-[:KNOWS]->(friend)
        RETURN friend.name AS name, friend.born AS born
        ORDER BY name
        """,
        name="Аня",
        database_="neo4j",
    )
    print(keys)
    for record in records:
        print(record["name"], record["born"], type(record["born"]).__name__)
$ python friends.py
['name', 'born']
Борис 1990 int
Глеб 1987 int

execute_query сам открывает сессию, выполняет запрос в транзакции и возвращает три вещи: записи, сводку и имена колонок. К полю записи обращаются по имени колонки, как к словарю. Целые числа Cypher стали обычными int в Python.

Параметры вместо склейки строк

В запросе стоит $name, а значение "Аня" передано отдельно. Это параметр. Никогда не вставляй значения в текст запроса через f-строки или +:

# так нельзя
driver.execute_query(f"MATCH (p:Person {{name: '{name}'}}) RETURN p")

Если в name окажется строка с кавычкой и куском Cypher, она станет частью запроса. Это та же инъекция, что и в SQL. С параметром значение всегда остаётся значением. Бонус: сервер кэширует план запроса по его тексту, и запрос с параметром не планируется заново для каждого имени.

Запись и сводка

Запись идёт тем же execute_query. Сводка summary рассказывает, что изменилось, это та же строка итога, что печатает cypher-shell:

from neo4j import GraphDatabase

URI = "neo4j://localhost:7687"
AUTH = ("neo4j", "secret-password")

with GraphDatabase.driver(URI, auth=AUTH) as driver:
    summary = driver.execute_query(
        """
        MERGE (p:Person {name: $name})
        ON CREATE SET p.born = $born
        """,
        name="Кира",
        born=1996,
        database_="neo4j",
    ).summary
    print("создано узлов:", summary.counters.nodes_created)
    print("записано свойств:", summary.counters.properties_set)
$ python write.py
создано узлов: 1
записано свойств: 2
$ python write.py
создано узлов: 0
записано свойств: 0

Второй запуск ничего не создал: MERGE работает и через драйвер.

JavaScript

Для Node.js драйвер ставится командой npm install neo4j-driver. Тот же запрос:

import neo4j from 'neo4j-driver';

const driver = neo4j.driver('neo4j://localhost:7687', neo4j.auth.basic('neo4j', 'secret-password'));

try {
  const { records, keys } = await driver.executeQuery(
    `MATCH (p:Person {name: $name})-[:KNOWS]->(friend)
     RETURN friend.name AS name, friend.born AS born
     ORDER BY name`,
    { name: 'Аня' },
    { database: 'neo4j' },
  );
  console.log(keys);
  for (const record of records) {
    console.log(record.get('name'), record.get('born'));
  }
  console.log(records[0].get('born').toNumber() + 1);
} finally {
  await driver.close();
}
$ node friends.mjs
[ 'name', 'born' ]
Борис Integer { low: 1990, high: 0 }
Глеб Integer { low: 1987, high: 0 }
1991

Вот главная ловушка JavaScript. Целые в Neo4j 64-битные, а обычное число JS точно хранит целые только до 2^53. Чтобы не терять точность, драйвер возвращает свой тип Integer. Для вывода и арифметики его превращают через toNumber(), а если все числа заведомо небольшие, можно создать драйвер с настройкой disableLosslessIntegers: true, и он будет отдавать обычные числа.

Несколько запросов в одной транзакции

execute_query выполняет один запрос в одной транзакции. Если нужно несколько запросов, которые должны пройти вместе или не пройти вовсе, открывают сессию и передают функцию с транзакцией: в Python session.execute_write(...), в JS session.executeWrite(...). Драйвер повторит такую функцию сам, если транзакция сорвалась из-за временной ошибки, например потери связи с кластером. Что такое транзакция в Neo4j и чем она защищает, разберём в следующем уроке.

Параметры в песочнице

Параметры есть и в консоли. В cypher-shell и в песочнице их задают командой :param, как будто это значение пришло из приложения:

:param name => 'Аня'
MATCH (p:Person {name: $name})-[:KNOWS]->(friend)
RETURN friend.name AS name, friend.born AS born
ORDER BY name;
neo4j@neo4j> :param name => 'Аня'
neo4j@neo4j> MATCH (p:Person {name: $name})-[:KNOWS]->(friend)
RETURN friend.name AS name, friend.born AS born
ORDER BY name;
+----------------+
| name    | born |
+----------------+
| "Борис" | 1990 |
| "Глеб"  | 1987 |
+----------------+

2 rows

Ответ тот же, что у скриптов на Python и JS. Команда :param без аргументов покажет все заданные параметры.

Попробуй сам

Песочница с параметрами: поменяй значение $name и выполни снова. Картинка покажет знакомых выбранного участника.

Задай второй параметр :param since => 2019 и добавь условие WHERE k.since >= $since. Выполни :param без аргументов и посмотри список параметров.

Проверь себя

Задание

Дальше

Драйвер выполняет запрос в транзакции, даже если мы об этом не просим. В следующем уроке разберёмся, что это значит в Neo4j: как устроены автоматические и явные транзакции, что происходит при ошибке и как база защищает данные при одновременной записи.

домашка

Домашка