关于 neo4j-driver-javascript-skill
neo4j-driver-javascript-skill 为使用 JavaScript 或 TypeScript 连接 Neo4j 的开发者提供驱动开发指导。内容包括查询与事务、数据类型、安全参数化查询及资源生命周期管理。
主要功能
- 01设置驱动、会话和事务
- 02执行查询及管理结果流
- 03处理整数与记录类型
- 04管理会话生命周期和错误
- 05使用参数化 Cypher 查询
适用场景
- 01为 Node.js 应用配置 Neo4j 驱动
- 02排查事务重试和结果流问题
- 03避免整数序列化精度损失
- 04修复会话泄漏或查询注入风险
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ neo4j-contrib/ neo4j-skills --skill neo4j-driver-javascript-skill安装后在 Claude Code 中输入/neo4j-driver-javascript-skill确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdreferences/advanced-patterns.mdbrowser-usage.mddata-types.mdrxjs-session.md详细介绍
适用场景
- 编写连接到 Neo4j 的 JS/TS 代码(Node.js 或浏览器)
- 配置驱动程序、会话、事务或查询执行
- 排查 Integer 处理、结果消费、会话泄漏、异步错误问题
- 为驱动程序对象添加 TypeScript 类型注解
不适用场景
- 编写或优化 Cypher →
neo4j-cypher-skill - 升级驱动程序版本 →
neo4j-migration-skill - RxJS 会话 API → references/rxjs-session.md
安装
npm install neo4j-driver # or: yarn add neo4j-driver
环境变量
从环境变量加载连接配置——切勿将凭据硬编码。
# .env file (add to .gitignore)
NEO4J_URI=neo4j+s://xxx.databases.neo4j.io
NEO4J_USERNAME=neo4j
NEO4J_PASSWORD=secret
NEO4J_DATABASE=neo4j
// npm install dotenv (for Node.js < 20 or when .env auto-load is off)
import 'dotenv/config' // or: require('dotenv').config()
const URI = process.env.NEO4J_URI
const USER = process.env.NEO4J_USERNAME
const PASSWORD = process.env.NEO4J_PASSWORD
const DATABASE = process.env.NEO4J_DATABASE ?? 'neo4j'
Node 20+ 原生支持通过 --env-file .env 加载 .env。Next.js / Vite 会自动加载 .env,无需导入 dotenv。
驱动程序生命周期
在启动时创建一个驱动程序实例,并在各处共享。切勿为每个请求单独创建。
// CommonJS
const neo4j = require('neo4j-driver')
// ESM / TypeScript
import neo4j from 'neo4j-driver'
const driver = neo4j.driver(
process.env.NEO4J_URI, // 'neo4j+s://xxx.databases.neo4j.io'
neo4j.auth.basic(process.env.NEO4J_USER, process.env.NEO4J_PASSWORD)
)
await driver.verifyConnectivity() // fail fast on startup if unreachable
// On shutdown:
await driver.close()
URI 协议:
| 协议 | 传输方式 | 用途 |
|---|---|---|
neo4j+s:// | TLS + 集群路由 | Aura;生产集群 |
neo4j:// | 明文 + 集群路由 | 本地开发集群 |
bolt+s:// | TLS,单实例 | 带 TLS 的单个 Neo4j 实例 |
bolt:// | 明文,单实例 | 本地单实例 |
身份验证选项:
neo4j.auth.basic(user, password) // username/password
neo4j.auth.bearer(token) // SSO / JWT
neo4j.auth.kerberos(base64Ticket) // Kerberos
neo4j.auth.none() // unauthenticated (dev only)
Web 框架中的单例——创建一次,在各处导入:
// db.js
let _driver = null
export function getDriver() {
if (!_driver) _driver = neo4j.driver(process.env.NEO4J_URI,
neo4j.auth.basic(process.env.NEO4J_USER, process.env.NEO4J_PASSWORD))
return _driver
}
export async function closeDriver() {
if (_driver) { await _driver.close(); _driver = null }
}
无服务器环境(Lambda/Vercel/Workers):将 maxConnectionPoolSize: 5 保持不变;无法保证会收到 SIGTERM。
选择合适的 API
| API | 适用场景 | 自动重试 | 结果 |
|---|---|---|---|
driver.executeQuery() | 大多数查询的默认选择 | ✅ | 立即返回(所有记录) |
session.executeRead/Write() | 大型结果集、流式处理、多查询事务 | ✅ | 惰性流 |
session.run() | LOAD CSV、CALL IN TRANSACTIONS、脚本 | ❌ | 惰性流 |
executeQuery — 默认选择
const { records, summary, keys } = await driver.executeQuery(
'MATCH (p:Person {name: $name})-[:KNOWS]->(f) RETURN f.name AS name',
{ name: 'Alice' },
{ database: 'neo4j', routing: neo4j.routing.READ }
)
for (const record of records) {
console.log(record.get('name')) // use .get() — records are NOT plain objects
}
// Write and count results
const { summary: s } = await driver.executeQuery(
'CREATE (p:Person {name: $name, age: $age})',
{ name: 'Bob', age: neo4j.int(30) },
{ database: 'neo4j' }
)
console.log(s.counters.updates().nodesCreated) // ✅ must call .updates()
始终指定 database——省略会导致额外的往返通信。
❌ 切勿使用模板字面量编写 Cypher:
// ❌ injection risk + disables plan caching
await driver.executeQuery(`MATCH (p:Person {name: '${name}'}) RETURN p`)
// ✅ parameterised
await driver.executeQuery('MATCH (p:Person {name: $name}) RETURN p', { name })
托管事务(executeRead / executeWrite)
遇到暂时性故障时会自动重试。在回调内消费记录——回调返回后,流就不可用了。
const session = driver.session({ database: 'neo4j' })
try {
const names = await session.executeRead(async tx => {
const result = await tx.run(
'MATCH (p:Person) WHERE p.name STARTS WITH $prefix RETURN p.name AS name',
{ prefix: 'Al' }
)
// ✅ collect() while tx is open; return plain data
return (await result.collect()).map(r => r.get('name'))
})
await session.executeWrite(async tx => {
await tx.run('MERGE (p:Person {name: $name})', { name: 'Carol' })
})
} finally {
await session.close() // always in finally
}
重要:await tx.run() 返回的是流句柄,而非记录。
// ❌ returns stream; tx closes; records = []
return await tx.run('MATCH (p:Person) RETURN p.name AS name')
// ✅ collect fully inside callback
const result = await tx.run('MATCH (p:Person) RETURN p.name AS name')
return (await result.collect()).map(r => r.get('name'))
// ✅ or stream with for-await
for await (const record of result) { names.push(record.get('name')) }
回调可能会执行多次(暂时性故障时重试)——不要在其中执行副作用:
// ❌ fetch() called on every retry
await session.executeWrite(async tx => {
await fetch('https://api.example.com/notify')
await tx.run('CREATE ...')
})
// ✅ side effects after confirmed commit
await session.executeWrite(async tx => { await tx.run('MERGE ...') })
await fetch('https://api.example.com/notify')
session.run — 隐式事务
不会自动重试。仅用于 LOAD CSV / CALL IN TRANSACTIONS / 脚本。
const session = driver.session({ database: 'neo4j' })
try {
const result = await session.run('CREATE (p:Person {name: $name}) RETURN p', { name: 'Alice' })
console.log(result.summary.counters.updates().nodesCreated)
} finally {
await session.close()
}
关闭会话——始终放在 finally 中
// ❌ session leaks if executeRead rejects
session.executeRead(async tx => { ... })
.then(result => doSomething(result))
.then(() => session.close())
// ✅ guaranteed close
try {
const result = await session.executeRead(async tx => { ... })
doSomething(result)
} finally {
await session.close()
}
// ✅ promise-chain equivalent
session.executeRead(async tx => { ... })
.then(doSomething)
.catch(handleError)
.finally(() => session.close())
Integer 处理
Neo4j 整数是 64 位;JS Number 使用 IEEE 754(安全范围上限为 2^53−1)。驱动程序默认返回自定义的 Integer 类型。
三种模式:
// Mode 1 (default): Integer class — safe for all values, requires conversion
const driver1 = neo4j.driver(URI, auth)
// record.get('count') → Integer { low: 42, high: 0 }
// Mode 2: native JS number — only safe within Number.MAX_SAFE_INTEGER
const driver2 = neo4j.driver(URI, auth, { disableLosslessIntegers: true })
// record.get('count') → 42
// Mode 3: BigInt — precise but breaks JSON.stringify
const driver3 = neo4j.driver(URI, auth, { useBigInt: true })
// record.get('count') → 42n
使用 Integer 类:
const count = record.get('count') // Integer { low: 42, high: 0 }
neo4j.isInt(count) // true
neo4j.integer.inSafeRange(count) // check before toNumber()
count.toNumber() // 42 (only safe within MAX_SAFE_INTEGER)
count.toString() // '42' (always safe)
count.toBigInt() // 42n
// Send integer parameter — plain JS number sends as FLOAT
await driver.executeQuery('CREATE (p:Person {age: $age})', { age: neo4j.int(30) })
JSON 序列化注意事项:
// ❌ Integer → {"low":42,"high":0}
JSON.stringify({ age: record.get('age') })
// ❌ BigInt → TypeError: Do not know how to serialize a BigInt
// ✅ convert first
JSON.stringify({ age: record.get('age').toNumber() })
// ✅ or use disableLosslessIntegers: true
// ❌ temporal types → {} silently
JSON.stringify({ dt: record.get('created') })
// ✅
JSON.stringify({ dt: record.get('created').toString() })
访问记录
记录不是普通对象——请使用 .get():
const record = records[0]
record.get('name') // ✅ by key
record.get(0) // ✅ by index
record.keys // ['name', 'age']
record.has('name') // true
record.name // ❌ undefined
record['name'] // ❌ undefined
record.toObject() 会返回键为普通 JS 键的对象,但值仍然是驱动程序类型(Integer、时间类型)。未经转换,不能安全地用于 JSON。
错误处理
import { Neo4jError, SERVICE_UNAVAILABLE, SESSION_EXPIRED } from 'neo4j-driver'
try {
await driver.executeQuery('...', {}, { database: 'neo4j' })
} catch (err) {
if (err instanceof Neo4jError) {
if (err.code === 'Neo.ClientError.Schema.ConstraintValidationFailed') { /* unique constraint */ }
if (err.code === SERVICE_UNAVAILABLE) { /* unreachable */ }
if (err.code === SESSION_EXPIRED) { /* open a new session */ }
if (err.retriable) { /* transient — executeQuery already retried to exhaustion */ }
}
}
executeQuery 和 executeRead/Write 会自动重试可重试的错误。session.run 不会。
TypeScript
import neo4j, { Driver, Session, ManagedTransaction, Record, Node, Integer } from 'neo4j-driver'
const driver: Driver = neo4j.driver(URI, neo4j.auth.basic(USER, PASSWORD))
const session: Session = driver.session({ database: 'neo4j' })
const names: string[] = await session.executeRead(
async (tx: ManagedTransaction): Promise<string[]> => {
const result = await tx.run('MATCH (p:Person) RETURN p.name AS name')
return (await result.collect()).map((r: Record) => r.get('name') as string)
}
)
// Typed node — Integer generic changes with disableLosslessIntegers
const node = record.get('p') as Node<Integer>
const age: number = node.properties.age.toNumber()
// With disableLosslessIntegers: true → Node<number>; age is already number
使用 UNWIND 批量写入
// ❌ one transaction per item
for (const p of people) { await driver.executeQuery('CREATE ...', p) }
// ✅ single transaction
await driver.executeQuery(
`UNWIND $people AS person
MERGE (p:Person {name: person.name})
SET p.age = person.age`,
{ people }, // array of plain objects; numeric fields must be JS numbers, not neo4j.int()
{ database: 'neo4j' }
)
常见错误
| 错误 | 修复方法 |
|---|---|
| 使用模板字符串拼接 Cypher | 使用 $param 占位符 |
使用 record.name / record['name'] | 使用 record.get('name') |
对 Integer 使用 JSON.stringify | 使用 .toNumber() 或 disableLosslessIntegers |
对 temporal 使用 JSON.stringify | 先调用 .toString() |
使用 summary.counters.nodesCreated | 使用 summary.counters.updates().nodesCreated |
未指定 database | 始终使用 { database: 'neo4j' } |
从事务回调中返回 result | 返回 await result.collect() 或映射后的数据 |
使用 .then(() => session.close()) | 使用 try/finally { await session.close() } |
| 在事务回调中执行副作用 | 移到外部——回调可能会重试 |
| 每个请求都创建新的 driver | 在启动时创建一次 |
在浏览器中使用 bolt:// 或 neo4j:// | 使用 neo4j+s://(WSS) |
| 在 UNWIND 数组中使用 Integer | 先转换为普通 JavaScript Number |
在无服务器环境中设置 maxConnectionPoolSize: 100 | 每个函数实例使用 5–10 |
参考资料
按需加载:
- references/data-types.md — 完整的类型映射表、temporal 类型、图类型(Node/Relationship/Path)、空间类型(Point/WGS-84/Cartesian)、
neo4j.UUID[6.2+]、toNative()转换辅助函数 - references/advanced-patterns.md — 显式事务、因果一致性/bookmarks、连接池调优、结果转换器、惰性流式处理、仓储模式
- references/browser-usage.md — WebSocket URI、CORS、打包工具配置、安全指南
- references/rxjs-session.md — RxJS 会话 API(
rxSession.run()、observable 模式)
检查清单
- 在启动时创建一个 driver 实例,并在各处共享
- 在每个查询/会话中指定
database - 在启动时调用
await driver.verifyConnectivity() - 在
finally块中调用session.close() - 使用
.get()访问记录——不要使用点号/方括号表示法 - 在 JSON 序列化之前调用 Integer 的
.toNumber()/.toString() - 在 JSON 序列化之前调用 temporal 的
.toString() - 在访问计数字段之前调用
summary.counters.updates() - 使用
$param占位符——不要在 Cypher 中进行字符串拼接 - 事务回调返回普通数据(而非流);在回调外执行副作用
- 写入时对整数参数使用
neo4j.int() - 读取使用
executeRead;写入使用executeWrite - 浏览器:使用
neo4j+s://URI(WebSocket)