Agent Skills

neo4j-driver-go-skill

Covers the Neo4j Go Driver v6 — driver lifecycle, ExecuteQuery, managed and explicit transactions, session config, error handling, data type mapping, and connection tuning. Use when writing Go code that connects to Neo4j, setting up NewDriver or ExecuteQuery, debugging sessions/transactions/result handling, or working with neo4j-go-driver v5→v6 migration. Triggers on NewDriver, ExecuteQuery, SessionConfig, ManagedTransaction, neo4j-go-driver. Does NOT handle Cypher query authoring — use neo4j-cy

Install

npx skills add https://github.com/neo4j-contrib/neo4j-skills --skill neo4j-driver-go-skill
SKILL.md

When to Use

  • Writing Go code that connects to Neo4j
  • Setting up neo4j.NewDriver(), ExecuteQuery(), or session/transaction patterns
  • Debugging connection errors, result iteration, type assertions, causal consistency

When NOT to Use

  • Writing/optimizing Cypher → neo4j-cypher-skill
  • v5→v6 migration steps → neo4j-migration-skill

Installation

go get github.com/neo4j/neo4j-go-driver/v6

Import: github.com/neo4j/neo4j-go-driver/v6/neo4j

v5→v6 rename (deprecated aliases still compile, remove before v7):

v5 v6
neo4j.NewDriverWithContext(...) neo4j.NewDriver(...)
neo4j.DriverWithContext neo4j.Driver

Environment Variables

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
}

Use godotenv to load .env in dev: godotenv.Load(). .env in .gitignore.


Driver Lifecycle

One Driver per application. Goroutine-safe, connection-pooled, expensive to create.

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)

❌ Never create driver per-request. Create once at startup; share across goroutines.

URI schemes: neo4j+s:// (Aura/TLS+routing), neo4j:// (plain+routing), bolt+s:// (TLS+single), bolt:// (plain+single).


Choosing the Right API

API Use when Auto-retry Lazy results
neo4j.ExecuteQuery() Most queries — simple default ✅ ❌ eager
session.ExecuteRead/Write() Large result sets / streaming ✅ ✅
session.BeginTransaction() Spans multiple functions / ext coordination ❌ ✅
session.Run() CALL IN TRANSACTIONS / auto-commit only ❌ ✅

CALL { … } IN TRANSACTIONS and USING PERIODIC COMMIT manage their own transactions — use session.Run(). They fail inside managed transactions.


ExecuteQuery (Recommended Default)

Manages sessions, transactions, retries, and bookmarks automatically.

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())

Key options:

neo4j.ExecuteQueryWithDatabase("mydb")          // required for performance
neo4j.ExecuteQueryWithReadersRouting()           // route reads to replicas
neo4j.ExecuteQueryWithImpersonatedUser("jane")  // impersonate
neo4j.ExecuteQueryWithoutBookmarkManager()       // opt out of causal consistency

❌ Never concatenate user input into query strings. Always use map[string]any parameters.

Struct parameters [v6.2.0+, Object Mapping preview] — a struct (or pointer) in params is sent as a Cypher map; same rules in 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"),
)

Managed Transactions (Session-Based)

Use for lazy streaming (large result sets) or callback-level control.

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()
    },
)

❌ No side effects in callback — retried on transient failures. ExecuteRead → replicas. ExecuteWrite → cluster leader.


Explicit Transactions

Use when transaction work spans multiple functions or requires external coordination.

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)

❌ Not auto-retried. Caller handles retry. Prefer managed transactions unless you need explicit control.


Error Handling

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)
}

Helpers:

neo4j.IsNeo4jError(err)                // server-side Cypher/database error
neo4j.IsTransactionExecutionLimit(err) // managed tx retries exhausted

In managed tx callback: return error → driver retries if transient. ConnectivityError at startup: check URI scheme, credentials, firewall.


Data Types

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; parse with dbtype.ParseUUID(s), render with .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

❌ Always check ok from record.Get() before type-asserting — panics on missing key. ❌ After lazy for res.Next(ctx) loop, always check res.Err().


Key Patterns

Context — always propagate

ctx, cancel := context.WithTimeout(parentCtx, 5*time.Second)
defer cancel()
// pass ctx to all driver calls

context.Background() has no deadline — slow queries block indefinitely.

Batching Writes

// 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"),
)

Generic Helpers (v6+)

Prefer type-safe helpers over manual assertions:

// 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")

Spatial Types

// 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))

Always Specify Database

neo4j.ExecuteQueryWithDatabase("neo4j")    // in ExecuteQuery
neo4j.SessionConfig{DatabaseName: "neo4j"} // in sessions

Omitting costs a network round-trip per call to resolve home database.

Causal Consistency

ExecuteQuery manages bookmarks automatically — no action needed for sequential calls. Cross-session (parallel workers): combine bookmarks explicitly — see references/repository-pattern.md.


Common Errors

Error / Symptom Cause Fix
ConnectivityError at startup URI wrong / TLS mismatch / firewall Check scheme (neo4j+s:// for Aura), credentials, port 7687
ConnectivityError mid-run Pool exhausted Increase MaxConnectionPoolSize; check for leaked sessions
Panic on type assertion record.Get() returned nil/wrong type Use neo4j.GetRecordValue[T]() or check ok first
res.Err() non-nil after loop Network error mid-stream Handle error; re-run transaction
Callback retried unexpectedly Side effect inside managed tx Move side effects outside callback
Context deadline exceeded No timeout on context Use context.WithTimeout
0 results, query looks correct Wrong DatabaseName Always set DatabaseName in config
CALL IN TRANSACTIONS fails Run inside managed tx Use session.Run() (auto-commit)

References

Load on demand:

  • references/advanced-config.md — connection pool tuning, custom address resolver, notification config, Bolt logging, auth options, URI scheme table, deprecated result-summary accessors (Profile() → QueryProfile())
  • references/repository-pattern.md — repository wrapper pattern, cross-session causal consistency with bookmarks

WebFetch

Need URL
Go driver manual https://neo4j.com/docs/go-manual/current/
API reference https://pkg.go.dev/github.com/neo4j/neo4j-go-driver/v6/neo4j

Checklist

  • One driver created at startup; shared across goroutines; defer driver.Close(ctx)
  • driver.VerifyConnectivity(ctx) called at startup
  • DatabaseName set in all SessionConfig / ExecuteQueryWithDatabase
  • context.WithTimeout used for production queries
  • map[string]any parameters used — no string interpolation
  • ExecuteQueryWithReadersRouting() on read-only ExecuteQuery calls
  • res.Err() checked after lazy for result.Next(ctx) loop
  • Type assertions guarded (use GetRecordValue[T] or check ok)
  • No side effects inside managed transaction callbacks
  • session.Run() used for CALL IN TRANSACTIONS / auto-commit queries
  • Sessions closed with defer session.Close(ctx)

Related skills

entra-app-registrationmicrosoft606KGuides Microsoft Entra ID app registration, OAuth 2.0 authentication, and MSAL integration. USE FOR: create app registration, register Azure AD app, configure OAuth, set up authentication, add API permissions, generate service principal, MSAL example, console app auth, Entra ID setup, Azure AD authentication. DO NOT USE FOR: Key Vault secrets (use azure-keyvault-expiration-audit), general Azure resource security guidance.azure-messagingmicrosoft595KTroubleshoot and resolve issues with Azure Messaging SDKs for Event Hubs and Service Bus. Covers connection failures, authentication errors, message processing issues, and SDK configuration problems. WHEN: event hub SDK error, service bus SDK issue, messaging connection failure, AMQP error, event processor host issue, message lock lost, message lock expired, lock renewal, lock renewal batch, send timeout, receiver disconnected, SDK troubleshooting, azure messaging SDK, event hub consumer, servicentra-agent-idmicrosoft328KProvision Microsoft Entra Agent Identity Blueprints, BlueprintPrincipals, and per-instance Agent Identities via Microsoft Graph, and configure OAuth 2.0 token exchange (fmi_path, OBO, cross-tenant) including the Microsoft Entra SDK for AgentID sidecar. USE FOR: Agent Identity Blueprint, BlueprintPrincipal, agent OAuth, fmi_path token exchange, agent OBO, Workload Identity Federation for agents, polyglot agent auth, Microsoft.Identity.Web.AgentIdentities. DO NOT USE FOR: standard Entra app registsupabasesupabase298KUse when doing ANY task involving Supabase. Triggers: Supabase products (Database, Auth, Edge Functions, Realtime, Storage, Vectors, Cron, Queues); client libraries and SSR integrations (supabase-js, @supabase/ssr) in Next.js, React, SvelteKit, Astro, Remix; auth issues (login, logout, sessions, JWT, cookies, getSession, getUser, getClaims, RLS); Supabase CLI or MCP server; schema changes, migrations, declarative schemas, security audits, Postgres extensions (pg_graphql, pg_cron, pg_vector); deb

Search skills and MCP servers

Fuzzy search across 23,137 skills and servers