Bolt driver for Elixir — talk to Neo4j (and other Bolt servers) over the binary Bolt protocol.
Bolty is built on DBConnection, so a
connection is a supervised, pooled process: start one with start_link/1
(or via child_spec/1 in a supervision tree) and query it with query/4 /
query_many/4.
Example
{:ok, conn} =
Bolty.start_link(
hostname: "localhost",
auth: [username: "neo4j", password: "password"]
)
{:ok, %Bolty.Response{} = res} =
Bolty.query(conn, "MATCH (n:Person) RETURN n.name AS name LIMIT $limit", %{limit: 5})
# A Response is Enumerable over its result rows
names = Enum.map(res, & &1["name"])See start_link/1 for the full connection options (TLS, pooling, auth). Every
failure — connection, TLS, version negotiation, and Neo4j server errors — is
surfaced as a Bolty.Error, so callers can match a single error shape.
Guides
- Public API & compatibility boundary — what is supported under semantic versioning
- Telemetry — the
[:bolty, :query]span and other events usage-rules.md— an agent-facing quick reference (shipped in the package)
Summary
Types
The basic authentication scheme relies on traditional username and password
Functions
Returns a supervisor child specification for a DBConnection pool.
Returns metadata about the negotiated connection.
Executes a single query and returns the result.
Executes a single query and returns the result.
Executes a batch of ;-separated statements and returns a list of results.
Same as query_many/4, but returns the list of Bolty.Response structs
directly and raises the Bolty.Error from the first failing statement.
Starts the connection process and connects to a Bolt/Neo4j server.
Lazily streams a query's result in batches, with server-side backpressure.
Runs fun inside a Bolt transaction, committing on return and rolling back on
rollback/2 or a raised error.
Types
The basic authentication scheme relies on traditional username and password
:username- Username (required):password- Password (default:nil)
@type conn() :: DBConnection.conn()
@type start_option() :: {:uri, String.t()} | {:hostname, String.t()} | {:port, :inet.port_number()} | {:scheme, String.t()} | {:routing, boolean()} | {:versions, [String.t() | float()]} | {:auth, basic_auth()} | {:user_agent, String.t()} | {:notifications_minimum_severity, String.t()} | {:notifications_disabled_categories, [String.t()]} | {:ssl_opts, [:ssl.tls_client_option()]} | {:connect_timeout, timeout()} | {:recv_timeout, timeout()} | {:socket_options, [:gen_tcp.connect_option()]} | DBConnection.start_option()
Functions
@spec child_spec([start_option()]) :: :supervisor.child_spec()
Returns a supervisor child specification for a DBConnection pool.
@spec connection_info(conn()) :: %{ bolt_version: String.t(), server_version: String.t(), policy: Bolty.Policy.t() }
Returns metadata about the negotiated connection.
Can be called with the conn passed into a transaction/3 callback or any
checked-out connection.
Example
Bolty.transaction(Bolt, fn conn ->
Bolty.connection_info(conn)
# => %{bolt_version: "5.8", server_version: "Neo4j/5.26.27", policy: %Bolty.Policy{...}}
end)
@spec query(conn(), String.t(), map(), keyword()) :: {:ok, Bolty.Response.t()} | {:error, Bolty.Error.t() | DBConnection.ConnectionError.t()}
Executes a single query and returns the result.
Returns {:ok, %Bolty.Response{}} on success. On failure it returns
{:error, %Bolty.Error{}} — every driver-side failure (connection, TLS,
version negotiation, and Neo4j server errors) is surfaced as a Bolty.Error,
so callers can match a single error shape. Pool checkout/timeout failures may
additionally surface as DBConnection.ConnectionError.
Options
:db- Target database for multi-database routing (default: server default).:mode- Access mode for this query,"w"(write, default) or"r"(read). Under server-side routing (aneo4j://connection to a cluster — seestart_link/1's:routingand the Clustering guide) the server uses this to forward the query to a writable or a read member. Without routing it is informational only.:bookmarks- A list of bookmark strings for causal consistency: the server will not run this query until it has caught up to the transactions those bookmarks identify. EachBolty.Responsecarries the bookmark of the transaction that produced it; thread the previous response's bookmark into the next query to chain reads-after-writes. There is no session abstraction, so bookmarks must be passed manually.:recv_timeout- Overrides the connection's:recv_timeoutfor this query (seestart_link/1). Pass:infinityfor a query expected to run longer than the connection default before returning data.
Examples
{:ok, result} = Bolty.query(conn, "MATCH (n) RETURN n LIMIT 1")
{:ok, people} = Bolty.query(conn, "MATCH (n:PERSON) RETURN n", %{}, [db: "mydb"])
@spec query!(conn(), String.t(), map(), keyword()) :: Bolty.Response.t()
Executes a single query and returns the result.
Examples
result = Bolty.query!(conn, "MATCH (n) RETURN n LIMIT 1")
people = Bolty.query!(conn, "MATCH (n:PERSON) RETURN n", %{}, [db: "mydb"])
@spec query_many(conn(), String.t(), map(), keyword()) :: {:ok, [Bolty.Response.t()]} | {:error, Bolty.Error.t() | DBConnection.ConnectionError.t()}
Executes a batch of ;-separated statements and returns a list of results.
The batch string is split into individual statements before each is run in
turn against the same connection. Execution stops at the first failure and
returns that {:error, %Bolty.Error{}}.
Statements are split on a top-level ; — one that is not inside a string
literal, a comment (// or /* */), or a backtick-quoted identifier — so a
; hiding inside any of those is safe. The terminating ; is dropped (Bolt
runs one bare statement at a time), and blank or comment-only segments are
skipped rather than sent to the server.
@spec query_many!(conn(), String.t(), map(), keyword()) :: [Bolty.Response.t()]
Same as query_many/4, but returns the list of Bolty.Response structs
directly and raises the Bolty.Error from the first failing statement.
@spec rollback(DBConnection.t(), any()) :: no_return()
@spec start_link([start_option()]) :: GenServer.on_start()
Starts the connection process and connects to a Bolt/Neo4j server.
Options
:uri- Connection URI, of the form<SCHEME>://<HOST>[:<PORT>[?policy=<POLICY-NAME>]]. Explicit:hostname,:portand:schemeoptions take priority over the corresponding URI components.:hostname- Server hostname (default:"localhost"):port- Server port (default:7687):scheme- Is one among neo4j, neo4j+s, neo4j+ssc, bolt, bolt+s, bolt+ssc (default: bolt+s). TLS is derived entirely from this:bolt/neo4jis plaintext,+sis full certificate verification against the OS trust store,+sscis encrypted but trust-all (self-signed certs). The scheme also sets the default for:routing(below).:routing- Whether to enable server-side routing (SSR) against a Neo4j Enterprise causal cluster, so the server forwards each statement to the right member based on the query's:mode/:bookmarks(seequery/4). Defaults totrueforneo4j*schemes andfalseforbolt*schemes, matching their meaning across the ecosystem; set it explicitly to override (routing: falsefor aneo4j://URI used cosmetically against a single instance or proxy,routing: trueto force it on abolt://scheme). See the Clustering guide for prerequisites and caveats.:versions- List of Bolt versions to offer during negotiation, as strings (["5.4", "6.0"]) or{major, minor..minor}range tuples ([{5, 6..8}]). Floats ([5.4]) are deprecated — they can't distinguish5.10from5.1— and are still accepted with a one-time warning. (Default: the latest supported versions.):auth- The basic authentication scheme:user_agent- Optionally override the default user agent name. (Default: 'bolty/<version>'):notifications_minimum_severity- Set the minimum severity for notifications the server should send to the client. Disabling severities allows the server to skip analysis for those, which can speed up query execution. (default: nil) New in neo4j v5.7 and Bolt v5.2:notifications_disabled_categories- Set categories of notifications the server should not send to the client. Disabling categories allows the server to skip analysis for those, which can speed up query execution. (default: nil) New in neo4j v5.7 and Bolt v5.2:connect_timeout- Socket connect timeout in milliseconds (default:15_000):recv_timeout- Per-read timeout in milliseconds for post-connect protocol reads (default:15_000). This is a socket-inactivity timeout: it fires only if the server sends no bytes for this long, and the window resets on each chunk received, so streaming a large result is unaffected. A read that times out is surfaced as{:error, %Bolty.Error{code: :timeout}}and the connection is disconnected. Can be overridden per query via the same option toquery/4; set to:infinityfor queries that may compute for a long time before returning any data.:ssl_opts- A list of:ssl.connect/2options, merged over the strict, secure-by-default TLS options bolty derives from:scheme(default:[]). An explicitverify:/cacertfile:/cacerts:here always overrides the scheme-derived default. Only applies when:schemeenables TLS (+sor+ssc); ignored for plainbolt/neo4j.
The given options are passed down to DBConnection, some of the most commonly used ones are documented below:
:after_connect- A function to run after the connection has been established, either a 1-arity fun, a{module, function, args}tuple, ornil(default:nil):pool- The pool module to use, defaults to built-in pool provided by DBconnection:pool_size- The size of the pool
@spec stream(DBConnection.t(), String.t(), map(), keyword()) :: DBConnection.Stream.t()
Lazily streams a query's result in batches, with server-side backpressure.
Returns a DBConnection stream that pulls records from the server in batches
as it is enumerated, rather than materialising the whole result up front like
query/4. Each element is a %Bolty.Response{} for one batch — its results
and records are that batch's records; the summary metadata (stats,
bookmark, …) lands on the final batch. Enumerating only part of the stream
fetches only the batches consumed; the rest is discarded server-side.
Must be called inside a transaction/4 — streaming pages against the
statement's server-assigned query id, which only exists within an explicit
transaction. Enumerate the stream within the same transaction callback.
Unlike query/4, a failure mid-stream is surfaced by raising a
Bolty.Error while the stream is being enumerated (the Enumerable protocol
has no error-tuple channel), rather than returning {:error, _}. The error
value is the same %Bolty.Error{}; the connection is RESET-recovered and
returns clean to the pool, and the enclosing transaction rolls back.
Options
:fetch_size- Records to pull per batch (the BoltPULL n). Default1000. Smaller batches mean more round-trips but lower peak memory.:mode,:bookmarks,:db- As documented onquery/4, applied to the streamed statement'sRUN.
Example
Bolty.transaction(conn, fn conn ->
conn
|> Bolty.stream("MATCH (n:Person) RETURN n", %{}, fetch_size: 500)
|> Stream.flat_map(& &1.results)
|> Enum.each(&process/1)
end)
@spec transaction( conn(), (DBConnection.t() -> result), [DBConnection.option()], map() ) :: {:ok, result} | {:error, any()} when result: var
Runs fun inside a Bolt transaction, committing on return and rolling back on
rollback/2 or a raised error.
opts are passed to DBConnection.transaction/3. extra_parameters is a map
applied to the opening BEGIN for the whole transaction:
:mode-"w"(write, default) or"r"(read). Under server-side routing (aneo4j://connection to a cluster — seestart_link/1's:routingand the Clustering guide) the server routes the transaction to a writable or a read member accordingly.:bookmarks- A list of bookmark strings for causal consistency; the transaction will not begin until the server has caught up to the transactions they identify. Read a completed transaction's bookmark from the lastBolty.Responseit produced and thread it into the next one — there is no session abstraction, so this is manual.:db- Target database for the transaction (default: server default).
Example
Bolty.transaction(conn, fn conn ->
Bolty.query!(conn, "CREATE (:Person {name: $n})", %{n: "Ada"})
end, [], %{mode: "w"})