Geode Client Libraries

Geode provides official client libraries for Go, Python, Rust, Zig, and Node.js/TypeScript. Each client uses the same QUIC + TLS 1.3 wire protocol while exposing language-idiomatic APIs for developers.

Introduction

Geode client libraries are designed with common principles:

  • Secure by Default: All connections use QUIC with TLS 1.3 encryption
  • High Performance: Native async support and connection pooling
  • Type Safety: Strong typing appropriate to each language
  • GQL Standard: ISO/IEC 39075:2024 compliance
  • Consistent Protocol: Protobuf wire protocol over QUIC streams

Choosing a Client Library

LanguageBest ForKey Features
GoMicroservices, APIsdatabase/sql driver, prepared statements
PythonData science, ML, scriptingAsync/await, query builders, RLS support
RustHigh-performance systemsZero-cost abstractions, tokio integration
ZigSystems programming, embeddedLow overhead, vendored QUIC
Node.jsWeb applications, APIsTypeScript support, async patterns

Go Client Library

The Go client implements the standard database/sql interface, making it familiar to Go developers and compatible with existing database tooling.

Installation

go get geodedb.com/geode

Quick Start

package main

import (
    "context"
    "database/sql"
    "fmt"
    "log"

    _ "geodedb.com/geode"
)

func main() {
    // Open connection using standard database/sql
    db, err := sql.Open("geode", "localhost:3141")
    if err != nil {
        log.Fatal(err)
    }
    defer db.Close()

    // Execute GQL queries
    rows, err := db.QueryContext(context.Background(),
        "MATCH (p:Person) WHERE p.age > $1 RETURN p.name, p.age",
        25,
    )
    if err != nil {
        log.Fatal(err)
    }
    defer rows.Close()

    for rows.Next() {
        var name string
        var age int
        if err := rows.Scan(&name, &age); err != nil {
            log.Fatal(err)
        }
        fmt.Printf("%s is %d years old\n", name, age)
    }
}

Connection String Options

// Full DSN with all options
dsn := "localhost:3141?" +
    "page_size=1000&" +
    "hello_name=myapp&" +
    "hello_ver=1.0.0&" +
    "conformance=full&" +
    "ca=/path/to/ca.crt&" +
    "cert=/path/to/client.crt&" +
    "key=/path/to/client.key&" +
    "insecure_tls_skip_verify=false"

db, err := sql.Open("geode", dsn)
OptionDescriptionDefault
page_sizeResults per page1000
hello_nameClient application name“geode-go”
hello_verClient version stringlibrary version
conformanceGQL conformance level“strict”
caCA certificate pathsystem CAs
certClient certificate pathnone
keyClient private key pathnone
insecure_tls_skip_verifySkip TLS verificationfalse

Transactions

func transferFunds(db *sql.DB, from, to string, amount float64) error {
    tx, err := db.BeginTx(context.Background(), nil)
    if err != nil {
        return err
    }
    defer tx.Rollback()

    // Debit source account
    _, err = tx.ExecContext(context.Background(), `
        MATCH (a:Account {id: $1})
        SET a.balance = a.balance - $2
    `, from, amount)
    if err != nil {
        return err
    }

    // Credit destination account
    _, err = tx.ExecContext(context.Background(), `
        MATCH (a:Account {id: $1})
        SET a.balance = a.balance + $2
    `, to, amount)
    if err != nil {
        return err
    }

    return tx.Commit()
}

Prepared Statements

// Prepare once, execute many times
stmt, err := db.PrepareContext(context.Background(),
    "MATCH (p:Product {category: $1}) RETURN p.name, p.price LIMIT $2")
if err != nil {
    log.Fatal(err)
}
defer stmt.Close()

// Execute with different parameters
categories := []string{"Electronics", "Books", "Clothing"}
for _, cat := range categories {
    rows, err := stmt.QueryContext(context.Background(), cat, 10)
    if err != nil {
        log.Fatal(err)
    }
    // Process rows...
    rows.Close()
}

Python Client Library

The Python client is async-first, built on aioquic for high-performance QUIC transport with full asyncio integration.

Installation

pip install geode-client

Quick Start

import asyncio
from geode_client import Client

async def main():
    client = Client(host="localhost", port=3141)

    async with client.connection() as conn:
        # Simple query
        page, _ = await conn.query(
            "MATCH (p:Person) RETURN p.name AS name, p.age AS age"
        )

        for row in page.rows:
            print(f"{row['name'].as_string}: {row['age'].as_int}")

asyncio.run(main())

Connection Pooling

from geode_client import Client, ConnectionPool

async def with_pool():
    # Create pool with configuration
    pool = ConnectionPool(
        host="localhost",
        port=3141,
        min_connections=5,
        max_connections=20,
        connection_timeout=30.0
    )

    await pool.initialize()

    try:
        # Acquire connection from pool
        async with pool.connection() as conn:
            result, _ = await conn.query("MATCH (n) RETURN count(n) AS total")
            print(f"Total nodes: {result.rows[0]['total'].as_int}")
    finally:
        await pool.close()

Parameterized Queries

async def find_users(conn, min_age: int, city: str):
    result, schema = await conn.query(
        """
        MATCH (u:User)-[:LIVES_IN]->(c:City {name: $city})
        WHERE u.age >= $min_age
        RETURN u.name AS name, u.email AS email, u.age AS age
        ORDER BY u.age DESC
        """,
        {"min_age": min_age, "city": city}
    )

    return [
        {
            "name": row['name'].as_string,
            "email": row['email'].as_string,
            "age": row['age'].as_int
        }
        for row in result.rows
    ]

Transactions with Savepoints

async def complex_operation(conn):
    async with conn.transaction() as tx:
        # Create user
        await tx.execute("""
            CREATE (u:User {user_id: $id, name: $name, email: $email})
        """, {"id": "user_123", "name": "Alice", "email": "[email protected]"})

        # Create savepoint before risky operation
        savepoint = await tx.savepoint("before_preferences")

        try:
            # This might fail
            await tx.execute("""
                MATCH (u:User {user_id: $id})
                CREATE (u)-[:PREFERS]->(:Category {name: $category})
            """, {"id": "user_123", "category": "InvalidCategory"})
        except Exception:
            # Rollback to savepoint, continue transaction
            await tx.rollback_to(savepoint)
            # Use default category instead
            await tx.execute("""
                MATCH (u:User {user_id: $id})
                CREATE (u)-[:PREFERS]->(:Category {name: 'General'})
            """, {"id": "user_123"})

        # Transaction commits successfully

Query Builders

from geode_client.query import QueryBuilder, PatternBuilder

# Build queries programmatically
query = (QueryBuilder()
    .match(PatternBuilder()
        .node("u", "User")
        .rel("r", "FOLLOWS", direction="out")
        .node("f", "User")
        .build())
    .where("u.user_id = $user_id")
    .return_("f.name AS friend_name", "r.since AS followed_since")
    .order_by("r.since DESC")
    .limit(10)
    .build())

result, _ = await conn.query(query, {"user_id": "user_456"})

Rust Client Library

The Rust client uses Quinn for QUIC transport and tokio for async execution, providing zero-cost abstractions and high performance.

Installation

[dependencies]
geode-client = "0.18"
tokio = { version = "1", features = ["full"] }

Quick Start

use geode_client::{Client, Config};
use tokio;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let config = Config::new("localhost", 3141);
    let client = Client::new(config)?;

    let mut conn = client.connect().await?;

    // Execute query
    let (page, schema) = conn.query(
        "MATCH (p:Person) RETURN p.name AS name, p.age AS age"
    ).await?;

    for row in page.rows {
        let name: String = row.get("name")?;
        let age: i64 = row.get("age")?;
        println!("{}: {} years old", name, age);
    }

    Ok(())
}

Typed Value Extraction

use geode_client::Value;

fn process_results(page: Page) -> Result<Vec<Person>, Error> {
    page.rows.iter().map(|row| {
        Ok(Person {
            id: row.get::<String>("id")?,
            name: row.get::<String>("name")?,
            age: row.get::<i64>("age")?,
            email: row.get::<Option<String>>("email")?,  // Nullable
            tags: row.get::<Vec<String>>("tags")?,       // List
            metadata: row.get::<HashMap<String, Value>>("meta")?, // Map
        })
    }).collect()
}

Connection Pooling

use geode_client::{Pool, PoolConfig};

async fn with_pool() -> Result<(), Error> {
    let pool_config = PoolConfig {
        min_connections: 5,
        max_connections: 50,
        connection_timeout: Duration::from_secs(30),
        idle_timeout: Duration::from_secs(300),
    };

    let pool = Pool::new("localhost", 3141, pool_config).await?;

    // Acquire connection from pool
    let conn = pool.get().await?;

    let (page, _) = conn.query("MATCH (n) RETURN count(n) AS c").await?;
    println!("Count: {}", page.rows[0].get::<i64>("c")?);

    // Connection returned to pool when dropped
    Ok(())
}

Query Builder

use geode_client::query::{Query, Pattern};

let query = Query::new()
    .match_pattern(
        Pattern::node("p", "Product")
            .with_property("category", &category)
    )
    .where_clause("p.price < $max_price")
    .return_fields(&["p.name", "p.price"])
    .order_by("p.price ASC")
    .limit(20)
    .build();

let (page, _) = conn.query_with_params(
    &query,
    params! {
        "max_price" => 100.0
    }
).await?;

Zig Client Library

The Zig client provides low-level control with vendored QUIC implementation, ideal for systems programming and embedded applications.

Installation

Add to your build.zig.zon:

.dependencies = .{
    .geode_client = .{
        .url = "https://github.com/geodedb/geode-client-zig/archive/v0.1.3.tar.gz",
        .hash = "...",
    },
},

Quick Start

const std = @import("std");
const geode = @import("geode_client");

pub fn main() !void {
    var gpa = std.heap.GeneralPurposeAllocator(.{}){};
    defer _ = gpa.deinit();
    const allocator = gpa.allocator();

    // Connect to Geode
    var client = try geode.Client.init(allocator, "localhost", 3141);
    defer client.deinit();

    // Execute query
    const result = try client.query(
        "MATCH (p:Person) RETURN p.name AS name, p.age AS age",
        &.{},
    );
    defer result.deinit();

    // Process results
    for (result.rows) |row| {
        const name = row.getString("name") orelse continue;
        const age = row.getInt("age") orelse continue;
        std.debug.print("{s}: {d} years old\n", .{ name, age });
    }
}

Parameterized Queries

const params = .{
    .min_age = @as(i64, 21),
    .city = "San Francisco",
};

const result = try client.query(
    \\MATCH (u:User)-[:LIVES_IN]->(c:City {name: $city})
    \\WHERE u.age >= $min_age
    \\RETURN u.name AS name, u.age AS age
, params);
defer result.deinit();

Transaction Support

// Begin transaction
var tx = try client.beginTransaction();
errdefer tx.rollback() catch {};

// Execute operations
try tx.execute(
    "CREATE (u:User {id: $id, name: $name})",
    .{ .id = "user_001", .name = "Bob" },
);

try tx.execute(
    "MATCH (u:User {id: $id}) CREATE (u)-[:OWNS]->(:Account {balance: 0})",
    .{ .id = "user_001" },
);

// Commit transaction
try tx.commit();

Node.js / TypeScript Client

The Node.js client provides full TypeScript support with async patterns familiar to JavaScript developers.

Installation

npm install @geodedb/client
# or
yarn add @geodedb/client

Quick Start (TypeScript)

import { Client, Connection } from '@geodedb/client';

async function main() {
    const client = new Client({
        host: 'localhost',
        port: 3141,
    });

    const conn = await client.connect();

    try {
        const { rows, schema } = await conn.query<{
            name: string;
            age: number;
        }>('MATCH (p:Person) RETURN p.name AS name, p.age AS age');

        for (const row of rows) {
            console.log(`${row.name}: ${row.age} years old`);
        }
    } finally {
        await conn.close();
    }
}

main().catch(console.error);

Type-Safe Queries

// Define result types
interface UserNode {
    userId: string;
    name: string;
    email: string;
    createdAt: Date;
}

interface FollowRelation {
    since: Date;
    strength: number;
}

// Query with type inference
const result = await conn.query<{
    user: UserNode;
    follows: UserNode;
    rel: FollowRelation;
}>(`
    MATCH (u:User {userId: $userId})-[r:FOLLOWS]->(f:User)
    RETURN u AS user, f AS follows, r AS rel
`, { userId: 'user_123' });

// TypeScript knows the types
for (const { user, follows, rel } of result.rows) {
    console.log(`${user.name} follows ${follows.name} since ${rel.since}`);
}

Connection Pool

import { Pool } from '@geodedb/client';

const pool = new Pool({
    host: 'localhost',
    port: 3141,
    minConnections: 5,
    maxConnections: 20,
    idleTimeout: 60000,
});

// Use pool
async function queryWithPool() {
    const conn = await pool.acquire();
    try {
        const result = await conn.query('MATCH (n) RETURN count(n) AS total');
        return result.rows[0].total;
    } finally {
        pool.release(conn);
    }
}

Wire Protocol Overview

All Geode clients use the same Protobuf wire protocol over QUIC (default) or gRPC:

Message Types

Client MessageDescription
HelloRequestConnection handshake with client info
ExecuteRequestExecute GQL query
PullRequestFetch result rows
BeginRequestStart transaction
CommitRequestCommit transaction
RollbackRequestRollback transaction
PingRequestKeep-alive
Server ResponseDescription
ExecutionResponseResponse wrapper with schema, data pages, errors, explain, or profile payloads

Connection Security

All connections require TLS 1.3:

# Custom TLS configuration (Python example)
client = Client(
    host="localhost",
    port=3141,
    tls_config=TLSConfig(
        ca_cert="/path/to/ca.crt",
        client_cert="/path/to/client.crt",
        client_key="/path/to/client.key",
        verify_hostname=True
    )
)

Further Reading

  • Client Comparison Guide: Feature matrix across all clients
  • Connection Pooling Best Practices: Optimize connection management
  • Error Handling: Working with GQL error codes
  • Performance Tuning: Maximizing client throughput
  • Migration Guides: Moving from other graph databases

Browse the tagged content below for comprehensive client library documentation, tutorials, and examples.


Related Articles