Работа из приложения: драйверы Python и JS
Как ваше приложение разговаривает с Neo4j: официальные драйверы, сессии и параметры запросов.
Драйвер Neo4j — официальная библиотека (Python, JavaScript, Java, Go, .NET), подключающаяся к серверу по протоколу Bolt и исполняющая Cypher из кода.
До сих пор мы писали Cypher в интерактивной консоли. Но в реальном продукте запросы исходят не от человека за консолью, а от кода: веб-сервер обрабатывает запрос пользователя, лезет в граф за рекомендациями и возвращает JSON. Мост между вашим приложением и Neo4j — это официальный драйвер. Он берёт на себя сетевое соединение, пул подключений, передачу параметров и разбор ответов, оставляя вам только текст Cypher и значения. Драйверы для всех языков устроены по одной логике, поэтому, поняв один, вы понимаете все. И есть пара правил — про параметры и про жизненный цикл драйвера, — которые отделяют рабочий код от дырявого и медленного.
Подключение по Bolt
Все драйверы устроены похоже: создаём драйвер с адресом bolt:// и учёткой, открываем сессию, выполняем Cypher. Пример на Python (библиотека neo4j):
from neo4j import GraphDatabase
driver = GraphDatabase.driver(
"bolt://localhost:7687",
auth=("neo4j", "secretpass"),
)
with driver.session() as session:
result = session.run(
"MATCH (p:Person {name:$name})-[:ЗНАЕТ]->(f) RETURN f.name AS friend",
name="Алиса",
)
for record in result:
print(record["friend"])
driver.close()Этот код помечен как language-text намеренно: ему нужна библиотека и живой сервер — в браузере он не исполнится. Обратите внимание на $name — это параметр.
Разберём скелет по шагам, потому что он одинаков во всех языках. GraphDatabase.driver(url, auth) создаёт объект драйвера: это «тяжёлый» долгоживущий объект, который сразу поднимает пул сетевых соединений к серверу. Префикс bolt:// в адресе — это не HTTP, а специальный бинарный протокол Neo4j (о нём ниже). Дальше driver.session() открывает лёгкую сессию — рабочий контекст под конкретную задачу; её мы оборачиваем в with, чтобы она гарантированно закрылась. Внутри session.run(cypher, params) отправляет запрос и возвращает поток записей, по которому мы итерируемся. Наконец driver.close() аккуратно гасит пул соединений при завершении приложения. Запомните пропорцию: драйвер — один на всё приложение, сессии — много и недолго.
Параметры — не конкатенация
Главное правило безопасности: никогда не склеивайте запрос строками. Передавайте значения как параметры ($name). Это защищает от инъекций и позволяет серверу кэшировать план запроса (одна и та же форма Cypher с разными значениями переиспользует скомпилированный план).
Чтобы прочувствовать опасность, представьте соблазн написать запрос конкатенацией: взять имя из формы и вклеить прямо в строку Cypher. Если пользователь введёт в поле что-то хитрое с кавычками и завершающими конструкциями, он сможет «дописать» ваш запрос — это та же инъекция, что и SQL-инъекция, только в графе. Параметр $name закрывает дыру в корне: значение едет на сервер отдельным каналом, рядом с текстом запроса, а не внутри него. Сервер никогда не пытается «исполнить» содержимое параметра как Cypher — для него это просто данные. Поэтому экранировать кавычки вручную не нужно: параметр не может сломать синтаксис в принципе.
Второй, менее очевидный выигрыш — производительность. Сервер кэширует план исполнения по тексту запроса. Запрос MATCH ... {name:$name} имеет один и тот же текст для всех имён, поэтому план компилируется один раз и переиспользуется. А вот склейка строк рождает уникальный текст на каждое имя — кэш промахивается, и сервер тратит время на перекомпиляцию плана при каждом вызове. Так что параметры — это и про безопасность, и про скорость одновременно.
// JavaScript (пакет neo4j-driver)
const neo4j = require('neo4j-driver');
const driver = neo4j.driver('bolt://localhost:7687',
neo4j.auth.basic('neo4j', 'secretpass'));
const session = driver.session();
const res = await session.run(
'MATCH (m:Movie) WHERE m.released > $year RETURN m.title AS t',
{ year: 2000 }
);
res.records.forEach(r => console.log(r.get('t')));
await session.close();
await driver.close();Сессии и транзакционные функции
Сессия — это логический контекст работы. Для надёжности драйверы предлагают транзакционные функции (execute_read / execute_write), которые сами повторяют запрос при временных сбоях (например, при переключении лидера в кластере). В продакшене пишут именно через них, а не через сырой session.run.
def get_friends(tx, name):
result = tx.run(
"MATCH (p:Person {name:$name})-[:ЗНАЕТ]->(f) RETURN f.name AS friend",
name=name,
)
return [r["friend"] for r in result]
with driver.session() as session:
friends = session.execute_read(get_friends, "Алиса")Здесь логика запроса вынесена в функцию, а execute_read её исполняет. Зачем эта обёртка? Она встроенно повторяет вызов при временных сбоях — например, в кластере лидер мог переключиться ровно в момент запроса. Сырой session.run в такой ситуации просто упал бы с ошибкой, а транзакционная функция тихо повторит попытку и вернёт результат. Поэтому важное требование: функцию-тело пишут идемпотентной и без побочных эффектов вне транзакции (никаких отправок писем внутри), ведь её могут вызвать дважды. Для чтения берут execute_read, для записи — execute_write; в кластере это ещё и подсказка драйверу, куда маршрутизировать запрос.
Как работает под капотом
Драйвер открывает Bolt-соединение (бинарный протокол поверх TCP/WebSocket), держит пул соединений и мультиплексирует по ним сессии. Cypher отправляется на сервер, тот компилирует/берёт из кэша план, исполняет и стримит записи обратно. Параметры передаются отдельно от текста запроса — поэтому они не могут «сломать» синтаксис и не требуют экранирования. В кластере драйвер ещё и маршрутизирует чтение на реплики, а запись — на лидера (об этом — урок о масштабировании).
Частые ошибки
- Склеивать значения в строку запроса. Это дыра для инъекций и убийца кэша планов; используйте
$параметры. - Не закрывать драйвер/сессию. Утечки соединений; используйте контекст-менеджеры (
with) илиfinally. - Создавать драйвер на каждый запрос. Драйвер тяжёлый и держит пул — он создаётся один раз на приложение, сессии — дёшевы.
- Игнорировать транзакционные функции. В кластере без ретраев запросы будут падать на переключениях.
Итоги
- Драйверы (Python/JS/…) подключаются по
bolt://и исполняют Cypher из кода. - Значения передавайте параметрами (
$name) — безопасность и кэш планов. - Драйвер создаётся один раз (пул соединений), сессии — лёгкие и краткоживущие.
- В продакшене используйте транзакционные функции с автоповтором (
execute_read/write).