Overview
Graph access from Go is query-strings-over-drivers: Cypher through the official Neo4j driver, Gremlin traversals through gremlin-go, DQL/GraphQL through Dgraph's dgo. There is no mapping layer of consequence — results come back as records and maps you unpack by hand — so the Go code stays thin and the modeling effort lives in the query language. Dgraph is the odd one out: a graph database written in Go itself, with a client that feels native rather than ported.
Key points
- Neo4j — neo4j-go-driver (
github.com/neo4j/neo4j-go-driver/v5/neo4j): one long-livedDriverWithContextper application (it owns the connection pool), short-lived sessions per work unit. Bolt protocol,neo4j://routing URIs for clusters. - Transaction functions are the idiom:
session.ExecuteRead/ExecuteWritetake a closure and retry it on transient cluster errors (leader switch, deadlock) — write the closure idempotently.ExecuteQueryis the one-shot convenience for single statements. - Results are records, not entities:
record.Get("n")returnsneo4j.Nodewith aProps map[string]any— you write the struct mapping;neo4j.GetProperty[T]helps with typed extraction. Parameters are always$parammaps — never fmt.Sprintf Cypher. - Gremlin — gremlin-go (Apache TinkerPop's official Go GLV): build traversals in Go
(
g.V().HasLabel("person").Out("knows")) against any TinkerPop-enabled store — JanusGraph, Neptune, Cosmos DB Gremlin API. The traversal source connects over a websocketDriverRemoteConnection. - Dgraph — dgo: gRPC client for a Go-native distributed graph DB; queries in DQL
(GraphQL-like), mutations as JSON or RDF N-Quads,
txn.Commit/Discardfor its transactional model. Dgraph also serves a spec-compliant GraphQL endpoint directly — sometimes no Go client code is needed at all. - When graph beats recursive SQL: multi-hop traversals of unpredictable depth (fraud rings, dependency chains, org charts, recommendations) where SQL needs recursive CTEs and each hop is another self-join. For one or two fixed hops on mostly tabular data, Postgres with a recursive CTE is simpler operationally — one store fewer.
- Testing: testcontainers-go has a dedicated neo4j module (
modules/neo4j) — spin the real store per test package; Gremlin code tests against a TinkerPop/Gremlin-server container; dgo against a Dgraph standalone image.
Details
Client landscape
| Store | Client | Query language | Transport |
|---|---|---|---|
| Neo4j, Aura | neo4j-go-driver v5 | Cypher | Bolt |
| JanusGraph, Neptune, Cosmos | gremlin-go | Gremlin traversals | WebSocket |
| Dgraph | dgo (or plain GraphQL over HTTP) | DQL / GraphQL | gRPC |
Neo4j session discipline
- Sessions are cheap and not goroutine-safe: create per request/work unit,
defer session.Close(ctx). The driver instance is the expensive, share-me object. - Causal consistency across sessions: pass bookmarks (
session.LastBookmarks()) into the next session's config when a read must observe a prior write on a cluster. - Everything takes
ctx— traversal timeouts are your context deadlines, same as every other Go client.
Examples
driver, _ := neo4j.NewDriverWithContext(uri, neo4j.BasicAuth(user, pass, ""))
defer driver.Close(ctx)
session := driver.NewSession(ctx, neo4j.SessionConfig{})
defer session.Close(ctx)
// Retryable write transaction: create a follows edge
_, err := session.ExecuteWrite(ctx,
func(tx neo4j.ManagedTransaction) (any, error) {
_, err := tx.Run(ctx,
`MATCH (a:User {id: $from}), (b:User {id: $to})
MERGE (a)-[:FOLLOWS {since: date()}]->(b)`,
map[string]any{"from": fromID, "to": toID})
return nil, err
})
// Multi-hop read: friends-of-friends, the query recursive SQL hates
records, err := session.ExecuteRead(ctx,
func(tx neo4j.ManagedTransaction) (any, error) {
res, err := tx.Run(ctx,
`MATCH (u:User {id: $id})-[:FOLLOWS*2..3]->(fof)
RETURN DISTINCT fof.id AS id LIMIT 50`,
map[string]any{"id": userID})
if err != nil {
return nil, err
}
return res.Collect(ctx)
})
Related
- Storage access from Go — the map — parent overview by storage kind.
- Document and NoSQL stores from Go — sibling non-relational clients with the same structs-and-contexts feel.
- Databases and other storage systems — graph storage models and index-free adjacency among the engine internals.
- Graph databases from Java — the JVM mirror: Spring Data Neo4j's mapped entities where Go unpacks records by hand.