Neo4j Driver Go Skill
Covers the Neo4j Go Driver v6 — driver lifecycle, ExecuteQuery, managed and
- Skill ID
- neo4j-contrib/neo4j-skills/neo4j-driver-go-skill
- Publisher
- neo4j-contrib
- Repository
- neo4j-skills
- Installs
- 0
- Files
- 4
- Synced
- Sep 23, 2026
Open any RiverX project, open the Skills panel in the chat, and search for this identifier. The files are fetched from the source repository at install time.
neo4j-contrib/neo4j-skills/neo4j-driver-go-skillInstalls these files- README.md
- SKILL.md
- references/advanced-config.md
- references/repository-pattern.md
What this skill tells the agent
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/v6Import: 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 exhaustedIn 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 callsMore from neo4j-contrib
Use this skill in a project
Open any RiverX project, search the Skills panel for neo4j-contrib/neo4j-skills/neo4j-driver-go-skill, and install it in one click.
