neo4j-driver-go-skill について
Go アプリケーションから Neo4j に接続する開発者向けのスキルガイドです。ドライバー設定、クエリとトランザクション、結果処理、エラー対応、性能と安全性の実践方法を扱います。
主な機能
- 01Neo4j Go Driver v6 の設定
- 02クエリとトランザクション API の選択
- 03結果型とエラーの処理
- 04接続、セッション、コンテキストの管理
- 05バッチ処理とパラメーター化クエリ
活用シーン
- 01Go アプリケーションから Neo4j へ接続
- 02接続や結果反復エラーの調査
- 03大きな結果セットでのストリーミング選択
- 04再試行を考慮したトランザクションの実装
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ neo4j-contrib/ neo4j-skills --skill neo4j-driver-go-skillインストール後、Claude Code で/neo4j-driver-go-skillを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
README.mdreferences/advanced-config.mdrepository-pattern.md詳細
使用する場面
- Neo4j に接続する Go コードを書く場合
neo4j.NewDriver()、ExecuteQuery()、またはセッション/トランザクションのパターンを設定する場合- 接続エラー、結果の反復処理、型アサーション、因果整合性をデバッグする場合
使用しない場面
- Cypher の作成/最適化 →
neo4j-cypher-skill - v5→v6 の移行手順 →
neo4j-migration-skill
インストール
go get github.com/neo4j/neo4j-go-driver/v6
インポート:github.com/neo4j/neo4j-go-driver/v6/neo4j
v5→v6 の名称変更(非推奨のエイリアスでも引き続きコンパイルできますが、v7 までに削除してください):
| v5 | v6 |
|---|---|
neo4j.NewDriverWithContext(...) | neo4j.NewDriver(...) |
neo4j.DriverWithContext | neo4j.Driver |
環境変数
import "os"
uri := getEnv("NEO4J_URI", "neo4j://localhost:7687")
user := getEnv("NEO4J_USERNAME", "neo4j")
password := getEnv("NEO4J_PASSWORD", "")
database := getEnv("NEO4J_DATABASE", "neo4j")
func getEnv(key, fallback string) string {
if v := os.Getenv(key); v != "" { return v }
return fallback
}
開発環境で .env を読み込むには godotenv を使用します:godotenv.Load()。.env は .gitignore に追加してください。
ドライバーのライフサイクル
アプリケーションごとに Driver を 1 つ使用します。ゴルーチンセーフで、コネクションプールを使用し、作成コストが高いためです。
func NewNeo4jDriver(uri, user, password string) (neo4j.Driver, error) {
driver, err := neo4j.NewDriver(
uri, // "neo4j+s://xxx.databases.neo4j.io" for Aura
neo4j.BasicAuth(user, password, ""),
)
if err != nil {
return nil, fmt.Errorf("create driver: %w", err)
}
ctx := context.Background()
if err := driver.VerifyConnectivity(ctx); err != nil {
driver.Close(ctx)
return nil, fmt.Errorf("verify connectivity: %w", err)
}
return driver, nil
}
// In main / app teardown:
defer driver.Close(ctx)
❌ リクエストごとにドライバーを作成しないでください。起動時に一度作成し、ゴルーチン間で共有します。
URI スキーム:neo4j+s://(Aura/TLS+ルーティング)、neo4j://(平文+ルーティング)、bolt+s://(TLS+単一接続)、bolt://(平文+単一接続)。
適切な API の選択
| API | 使用する場面 | 自動再試行 | 遅延結果 |
|---|---|---|---|
neo4j.ExecuteQuery() | ほとんどのクエリ — シンプルなデフォルト | ✅ | ❌ 即時 |
session.ExecuteRead/Write() | 大きな結果セット/ストリーミング | ✅ | ✅ |
session.BeginTransaction() | 複数の関数にまたがる処理/外部システムとの連携 | ❌ | ✅ |
session.Run() | CALL IN TRANSACTIONS/自動コミットのみ | ❌ | ✅ |
CALL { … } IN TRANSACTIONS と USING PERIODIC COMMIT は独自にトランザクションを管理します。session.Run() を使用してください。管理対象トランザクション内では失敗します。
ExecuteQuery(推奨デフォルト)
セッション、トランザクション、再試行、ブックマークを自動的に管理します。
result, err := neo4j.ExecuteQuery(ctx, driver,
`MATCH (p:Person {name: $name})-[:KNOWS]->(friend)
RETURN friend.name AS name`,
map[string]any{"name": "Alice"},
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"), // always specify
neo4j.ExecuteQueryWithReadersRouting(), // for read queries
)
if err != nil {
return fmt.Errorf("query people: %w", err)
}
for _, record := range result.Records {
name, _ := record.Get("name")
fmt.Println(name)
}
fmt.Println(result.Summary.Counters().NodesCreated())
主なオプション:
neo4j.ExecuteQueryWithDatabase("mydb") // required for performance
neo4j.ExecuteQueryWithReadersRouting() // route reads to replicas
neo4j.ExecuteQueryWithImpersonatedUser("jane") // impersonate
neo4j.ExecuteQueryWithoutBookmarkManager() // opt out of causal consistency
❌ ユーザー入力をクエリ文字列に連結しないでください。必ず map[string]any のパラメーターを使用してください。
構造体パラメーター(v6.2.0 以降、オブジェクトマッピングのプレビュー):params 内の構造体(またはポインター)は Cypher のマップとして送信されます。同じルールが Session.Run、ManagedTransaction.Run、ExplicitTransaction.Run に適用されます:
type Person struct {
Name string `neo4j:"name"`
Age int `neo4j:"age,omitempty"` // zero value dropped
Internal string `neo4j:"-"` // never sent
}
_, err := neo4j.ExecuteQuery(ctx, driver,
"MERGE (p:Person {name: $person.name}) SET p += $person",
map[string]any{"person": Person{Name: "Alice", Age: 30}},
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"),
)
管理対象トランザクション(セッションベース)
遅延ストリーミング(大きな結果セット)やコールバックレベルの制御が必要な場合に使用します。
session := driver.NewSession(ctx, neo4j.SessionConfig{
DatabaseName: "neo4j", // always specify
AccessMode: neo4j.AccessModeRead,
})
defer session.Close(ctx)
result, err := session.ExecuteRead(ctx,
func(tx neo4j.ManagedTransaction) (any, error) {
res, err := tx.Run(ctx,
`MATCH (p:Person) RETURN p.name AS name LIMIT $limit`,
map[string]any{"limit": 100},
)
if err != nil {
return nil, err
}
var names []string
for res.Next(ctx) { // lazy — don't Collect() on large sets
name, _ := res.Record().Get("name")
names = append(names, name.(string))
}
return names, res.Err()
},
)
❌ コールバック内で副作用を発生させないでください。一時的な障害時に再試行されます。
ExecuteRead → レプリカ。ExecuteWrite → クラスターリーダー。
明示的なトランザクション
トランザクション処理が複数の関数にまたがる場合や、外部との連携が必要な場合に使用します。
session := driver.NewSession(ctx, neo4j.SessionConfig{DatabaseName: "neo4j"})
defer session.Close(ctx)
tx, err := session.BeginTransaction(ctx)
if err != nil {
return err
}
if err := doPartA(ctx, tx); err != nil {
tx.Rollback(ctx)
return err
}
if err := doPartB(ctx, tx); err != nil {
tx.Rollback(ctx)
return err
}
return tx.Commit(ctx)
❌ 自動再試行されません。呼び出し元が再試行を処理します。明示的な制御が必要でない限り、管理対象トランザクションを優先してください。
エラー処理
result, err := neo4j.ExecuteQuery(...)
if err != nil {
var neo4jErr *neo4j.Neo4jError
if errors.As(err, &neo4jErr) {
slog.Error("database error", "code", neo4jErr.Code, "msg", neo4jErr.Msg)
}
var connErr *neo4j.ConnectivityError
if errors.As(err, &connErr) {
slog.Error("connectivity error", "err", connErr)
}
return fmt.Errorf("execute query: %w", err)
}
ヘルパー:
neo4j.IsNeo4jError(err) // server-side Cypher/database error
neo4j.IsTransactionExecutionLimit(err) // managed tx retries exhausted
管理対象トランザクションのコールバックでは、エラーを返すと、一時的なエラーの場合にドライバーが再試行します。
起動時に ConnectivityError が発生する場合は、URI スキーム、認証情報、ファイアウォールを確認してください。
データ型
| Cypher | Go |
|---|---|
Integer | int64 |
Float | float64 |
String | string |
Boolean | bool |
List | []any |
Map | map[string]any |
Node | neo4j.Node |
Relationship | neo4j.Relationship |
Path | neo4j.Path |
Date | neo4j.Date |
DateTime | neo4j.Time |
Duration | neo4j.Duration |
UUID | neo4j.UUID(= dbtype.UUID、[16]byte RFC 9562)— v6.2.0 以降、Bolt 6.1。dbtype.ParseUUID(s) で解析し、.String() で文字列に変換 |
null | nil |
// Typed extraction (v6+, preferred):
neo4j.GetRecordValue[string](record, "name")
// Manual extraction:
rawAge, ok := record.Get("age")
if !ok { return errors.New("missing 'age' field") }
age := rawAge.(int64) // Neo4j integers → int64
// Node access:
rawNode, _ := record.Get("p")
node := rawNode.(neo4j.Node)
name := node.Props["name"].(string)
labels := node.Labels // []string
❌ 型アサーションの前に、必ず record.Get() の ok を確認してください。キーがない場合、パニックが発生します。
❌ 遅延評価される for res.Next(ctx) ループの後は、必ず res.Err() を確認してください。
主なパターン
Context — 常に伝播させる
ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
defer cancel()
// pass ctx to all driver calls
context.Background() には期限がないため、遅いクエリが無期限にブロックします。
書き込みのバッチ処理
// Bad: one transaction per record
for _, item := range items {
neo4j.ExecuteQuery(ctx, driver, writeQuery, item, ...)
}
// Good: UNWIND batch in one transaction
neo4j.ExecuteQuery(ctx, driver,
`UNWIND $items AS item
MERGE (n:Node {id: item.id})
SET n += item`,
map[string]any{"items": items},
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"),
)
汎用ヘルパー(v6+)
手動で型アサーションを行うより、型安全なヘルパーを優先してください。
// GetRecordValue[T] — extract + cast in one call
name, isNil, err := neo4j.GetRecordValue[string](record, "name")
// isNil=true when OPTIONAL MATCH returned null; err != nil when key absent or wrong type
// CollectTWithContext — map all records to a slice
people, err := neo4j.CollectTWithContext(ctx, result, func(record *neo4j.Record) (Person, error) {
name, _, err := neo4j.GetRecordValue[string](record, "name")
age, _, _ := neo4j.GetRecordValue[int64](record, "age")
return Person{Name: name, Age: int(age)}, err
})
// SingleTWithContext — expect exactly one record (error if 0 or 2+)
person, err := neo4j.SingleTWithContext(ctx, result, func(record *neo4j.Record) (Person, error) {
name, _, _ := neo4j.GetRecordValue[string](record, "name")
return Person{Name: name}, nil
})
// GetProperty — typed property from Node or Relationship
node, _, _ := neo4j.GetRecordValue[neo4j.Node](record, "p")
nameVal, err := neo4j.GetProperty[string](node, "name")
結果のマッピング[v6.3.0+、オブジェクトマッピングプレビュー]— レコードを neo4j タグ付きの構造体にマッピングします(構造体パラメーターと同じタグを使用します)。
type Person struct {
Name string `neo4j:"name"`
Age int `neo4j:"age"` // INTEGER converted; lossy conversion → error, not truncation
}
// EagerResult from ExecuteQuery
res, err := neo4j.ExecuteQuery(ctx, driver,
"MATCH (p:Person) RETURN p.name AS name, p.age AS age LIMIT 25",
nil, neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase("neo4j"))
people, err := neo4j.CollectRecordsAs[Person](res.Records)
// Streaming Result inside a transaction
all, err := neo4j.CollectAs[Person](ctx, result) // all records
person, err := neo4j.SingleAs[Person](ctx, result) // exactly one record
p, err := neo4j.As[Person](record) // single record
マッピングのルール:ノード、リレーションシップ、またはマップを含む列が1つの場合、そのプロパティがフィールドにマッピングされます。それ以外の場合は、列名でマッピングされます。欠損値/null はゼロ値になり、余分な列は無視されます。
空間型
// 2D Cartesian (SRID 7203), 3D Cartesian (SRID 9157)
pt2d := neo4j.Point2D{X: 1.23, Y: 4.56, SpatialRefId: 7203}
pt3d := neo4j.Point3D{X: 1.23, Y: 4.56, Z: 7.89, SpatialRefId: 9157}
// 2D WGS-84 (SRID 4326), 3D WGS-84 (SRID 4979)
london := neo4j.Point2D{X: -0.118092, Y: 51.509865, SpatialRefId: 4326}
shard := neo4j.Point3D{X: -0.0865, Y: 51.5045, Z: 310, SpatialRefId: 4979}
// Pass as parameter
result, err := neo4j.ExecuteQuery(ctx, driver,
"CREATE (p:Place {location: $loc})",
map[string]any{"loc": london},
neo4j.EagerResultTransformer,
neo4j.ExecuteQueryWithDatabase("neo4j"),
)
// Read from result — assert to Point2D or Point3D
raw, _ := record.Get("location")
if p2d, ok := raw.(neo4j.Point2D); ok {
fmt.Printf("lon=%f lat=%f srid=%d\n", p2d.X, p2d.Y, p2d.SpatialRefId)
}
// Distance (same SRID only)
result, _ = neo4j.ExecuteQuery(ctx, driver,
"RETURN point.distance($p1, $p2) AS distance",
map[string]any{"p1": pt2d, "p2": neo4j.Point2D{X: 10, Y: 10, SpatialRefId: 7203}},
neo4j.EagerResultTransformer, neo4j.ExecuteQueryWithDatabase("neo4j"),
)
dist, _ := result.Records[0].Get("distance")
fmt.Println(dist.(float64))
データベースを必ず指定する
neo4j.ExecuteQueryWithDatabase("neo4j") // in ExecuteQuery
neo4j.SessionConfig{DatabaseName: "neo4j"} // in sessions
省略すると、ホームデータベースを特定するために、呼び出しごとにネットワークのラウンドトリップが発生します。
因果的一貫性
ExecuteQuery はブックマークを自動的に管理するため、逐次呼び出しでは何もする必要はありません。
セッションをまたぐ場合(並列ワーカー):ブックマークを明示的に組み合わせてください。references/repository-pattern.md を参照してください。
よくあるエラー
| エラー/症状 | 原因 | 対処法 |
|---|---|---|
起動時に ConnectivityError が発生する | URI の誤り/TLS の不一致/ファイアウォール | スキーム(Aura の場合は neo4j+s://)、認証情報、ポート 7687 を確認します |
実行中に ConnectivityError が発生する | プールの枯渇 | MaxConnectionPoolSize を増やし、セッションの閉じ忘れがないか確認します |
| 型アサーションでパニックが発生する | record.Get() が nil または誤った型を返した | neo4j.GetRecordValue[T]() を使用するか、先に ok を確認します |
ループ後に res.Err() が nil 以外になる | ストリーム中のネットワークエラー | エラーを処理し、トランザクションを再実行します |
| コールバックが予期せず再試行される | 管理対象トランザクション内で副作用が発生している | 副作用をコールバックの外に移します |
| コンテキストの期限を超過した | コンテキストにタイムアウトが設定されていない | context.WithTimeout を使用します |
| クエリが正しく見えるのに結果が 0 件 | DatabaseName が間違っている | 設定で必ず DatabaseName を指定します |
CALL IN TRANSACTIONS が失敗する | 管理対象トランザクション内で実行している | session.Run()(自動コミット)を使用します |
参考資料
必要に応じて読み込んでください。
- references/advanced-config.md — 接続プールの調整、カスタムアドレスリゾルバー、通知設定、Bolt のログ記録、認証オプション、URI スキーム一覧、非推奨の結果サマリーアクセサー(
Profile()→QueryProfile()) - references/repository-pattern.md — リポジトリーラッパーパターン、ブックマークを使用したセッションをまたぐ因果的一貫性
WebFetch
| 必要な情報 | URL |
|---|---|
| Go ドライバーマニュアル | https://neo4j.com/docs/go-manual/current/ |
| API リファレンス | https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v6/neo4j |
チェックリスト
- 起動時にドライバーを1つ作成し、ゴルーチン間で共有する。
defer driver.Close(ctx)を使用する - 起動時に
driver.VerifyConnectivity(ctx)を呼び出す - すべての
SessionConfig/ExecuteQueryWithDatabaseでDatabaseNameを設定する - 本番クエリでは
context.WithTimeoutを使用する - パラメーターには
map[string]anyを使用し、文字列補間を行わない - 読み取り専用の
ExecuteQuery呼び出しではExecuteQueryWithReadersRouting()を使用する - 遅延評価される
for result.Next(ctx)ループの後にres.Err()を確認する - 型アサーションをガードする(
GetRecordValue[T]を使用するか、okを確認する) - 管理対象トランザクションのコールバック内で副作用を発生させない
-
CALL IN TRANSACTIONS/自動コミットクエリにはsession.Run()を使用する -
defer session.Close(ctx)でセッションを閉じる