mcpField
N
neo4j-driver-javascript-skill

neo4j-driver-javascript-skill

github.com/neo4j-contrib開発118407 回インストール1 週間前に更新
リポジトリ

neo4j-driver-javascript-skill について

neo4j-driver-javascript-skill は、JavaScript または TypeScript で Neo4j に接続する開発者向けのドライバーガイドです。クエリとトランザクション、データ型、パラメーター化クエリ、安全なリソース管理を扱います。

主な機能

  1. 01ドライバー、セッション、トランザクションの設定
  2. 02クエリ実行と結果ストリームの管理
  3. 03整数とレコード型の処理
  4. 04セッションのライフサイクルとエラー管理
  5. 05パラメーター化 Cypher クエリの利用

活用シーン

  1. 01Node.js アプリ向け Neo4j ドライバーの設定
  2. 02トランザクション再試行や結果ストリームの調査
  3. 03シリアライズ時の整数精度損失の回避
  4. 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.md
references/
advanced-patterns.md
browser-usage.md
data-types.md
rxjs-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 のインポートは不要です。


ドライバーのライフサイクル

起動時にドライバーインスタンスを 1 つだけ作成します。どこからでも共有してください。リクエストごとに作成しないでください。

// 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()大きな結果、ストリーミング、複数クエリの tx✅遅延ストリーム
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 を返します。

3 つのモード:

// 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
時間型に対する JSON.stringify先に .toString() を呼び出します
summary.counters.nodesCreatedsummary.counters.updates().nodesCreated
database を省略する常に { database: 'neo4j' } を指定します
トランザクションコールバックから result を返すawait result.collect() またはマッピングしたデータを返します
.then(() => session.close())try/finally { await session.close() }
トランザクションコールバック内で副作用を発生させる外に移します — コールバックは再試行される場合があります
リクエストごとに新しいドライバーを作成する起動時に一度だけ作成します
ブラウザーで bolt:// または neo4j:// を使用するneo4j+s://(WSS)を使用します
UNWIND 配列内の Integer先に通常の JS Number に変換します
サーバーレス環境で maxConnectionPoolSize: 100 を指定する関数インスタンスごとに 5~10 を使用します

リファレンス

必要に応じて読み込みます:

  • references/data-types.md — 型マッピング表の全容、時間型、グラフ型(Node/Relationship/Path)、空間型(Point/WGS-84/Cartesian)、neo4j.UUID [6.2+]、toNative() 変換ヘルパー
  • references/advanced-patterns.md — 明示的なトランザクション、因果整合性/ブックマーク、接続プールの調整、結果トランスフォーマー、遅延ストリーミング、リポジトリパターン
  • references/browser-usage.md — WebSocket URI、CORS、バンドラー設定、セキュリティガイダンス
  • references/rxjs-session.md — RxJS セッション API(rxSession.run()、Observable パターン)

チェックリスト

  • 起動時にドライバーインスタンスを 1 つ作成し、すべての場所で共有する
  • すべてのクエリ/セッションで database を指定する
  • 起動時に await driver.verifyConnectivity() を呼び出す
  • finally ブロックで session.close() を呼び出す
  • レコードにはドット/ブラケット記法ではなく .get() でアクセスする
  • JSON シリアライズの前に Integer に対して .toNumber()/.toString() を呼び出す
  • JSON シリアライズの前に時間型に対して .toString() を呼び出す
  • カウンターフィールドにアクセスする前に summary.counters.updates() を呼び出す
  • $param プレースホルダーを使用し、Cypher で文字列連結をしない
  • トランザクションコールバックからプレーンデータを返す(ストリームは返さない)。副作用はコールバックの外で発生させる
  • 書き込みでは整数パラメーターに neo4j.int() を使用する
  • 読み取りには executeRead、書き込みには executeWrite を使用する
  • ブラウザーでは neo4j+s:// URI(WebSocket)を使用する

コメント