Development Guide
This comprehensive guide covers everything developers need to build applications with Geode, from initial setup through testing, debugging, and deployment. Whether you’re building web applications, microservices, or data pipelines, this guide provides the foundation for successful Geode integration.
Getting Started
Prerequisites
System Requirements:
- Linux, macOS, or Windows (WSL2)
- 4GB RAM minimum (8GB+ recommended)
- 2GB free disk space
- Modern compiler toolchain
For Server Development:
- Zig 0.1.0+ (for building from source)
- Git
For Client Development:
- Go 1.24+ (for Go client)
- Python 3.9+ (for Python client)
- Rust 1.70+ (for Rust client)
- Node.js 18+ (if building JavaScript client)
Installation
Option 1: Docker (Fastest):
# Pull and run Geode
docker pull geodedb/geode:latest
docker run -d -p 3141:3141 --name geode geodedb/geode:latest
# Verify
docker logs geode
Option 2: Build from Source:
# Clone repository
git clone https://github.com/codeprosorg/geode
cd geode
# Build
make build
# Run server
./zig-out/bin/geode serve --listen 127.0.0.1:3141
# In another terminal
./zig-out/bin/geode shell
Option 3: Package Manager (coming soon):
# Homebrew (macOS/Linux)
brew install geodedb/geode/geode
# APT (Debian/Ubuntu)
sudo apt install geode
# Snap (Linux)
sudo snap install geode
Development Environment Setup
Local Development Server
Create a development configuration:
# config/dev.yaml
server:
listen: "127.0.0.1:3141"
log_level: "debug"
storage:
data_dir: "./data/dev"
wal_dir: "./data/wal"
security:
tls_enabled: false # Disable for local dev only
performance:
query_timeout: 30s
max_connections: 100
Start server:
./geode serve --config config/dev.yaml
Database Shell
The interactive shell is essential for development:
# Connect to local server
./geode shell
# Or specify remote server
./geode shell --host geode.example.com:3141
Shell commands:
-- Execute queries
MATCH (p:Person) RETURN p.name LIMIT 10;
-- Show schema
SHOW LABELS;
SHOW INDEXES;
-- Measure timing
.timer on
MATCH (p:Person)-[:KNOWS*2..3]->(friend) RETURN count(friend);
-- Export results
.output results.json
MATCH (p:Person) RETURN p;
-- Help
.help
Client Library Integration
Go Client
Installation:
go get geodedb.com/geode
Basic Usage:
package main
import (
"context"
"database/sql"
"fmt"
"log"
_ "geodedb.com/geode"
)
func main() {
// Connect
db, err := sql.Open("geode", "quic://localhost:3141")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Ping to verify connection
if err := db.PingContext(context.Background()); err != nil {
log.Fatal(err)
}
// Execute query
rows, err := db.QueryContext(
context.Background(),
"MATCH (p:Person) WHERE p.age > $age RETURN p.name, p.city",
sql.Named("age", 30),
)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// Process results
for rows.Next() {
var name, city string
if err := rows.Scan(&name, &city); err != nil {
log.Fatal(err)
}
fmt.Printf("%s from %s\n", name, city)
}
}
Advanced Features:
// Transactions
tx, err := db.BeginTx(context.Background(), nil)
if err != nil {
return err
}
defer tx.Rollback()
_, err = tx.ExecContext(ctx, "INSERT (u:User {name: $name})", sql.Named("name", "Alice"))
if err != nil {
return err
}
if err := tx.Commit(); err != nil {
return err
}
// Prepared statements
stmt, err := db.PrepareContext(ctx, "MATCH (p:Person {id: $id}) RETURN p.name")
defer stmt.Close()
for _, id := range userIDs {
var name string
err := stmt.QueryRowContext(ctx, sql.Named("id", id)).Scan(&name)
// ...
}
Python Client
Installation:
pip install geode-client
Basic Usage:
import asyncio
from geode_client import Client
async def main():
# Connect
client = Client(host="localhost", port=3141)
async with client.connection() as conn:
# Execute query
result, _ = await conn.query(
"MATCH (p:Person) WHERE p.age > $age RETURN p.name, p.city",
parameters={"age": 30}
)
# Process results
for row in result.rows:
print(f"{row['p.name']} from {row['p.city']}")
asyncio.run(main())
Advanced Features:
# Transactions
async with client.connection() as tx:
await tx.begin()
await tx.execute(
"INSERT (u:User {name: $name})",
{"name": "Alice"}
)
await tx.execute(
"INSERT (u:User {name: $name})",
{"name": "Bob"}
)
await tx.commit()
# Connection pooling
client = Client(
"localhost:3141",
pool_size=20,
max_overflow=10
)
# Query builder
from geode_client import QueryBuilder
query = (
QueryBuilder()
.match("(p:Person)")
.where("p.age > $age")
.return_("p.name", "p.city")
.order_by("p.name")
.limit(10)
)
result, _ = await tx.query(query.build(), {"age": 30})
Rust Client
Installation:
[dependencies]
geode-client = "0.1"
tokio = { version = "1", features = ["full"] }
Basic Usage:
use geode_client::{Client, Error, Value};
use std::collections::HashMap;
#[tokio::main]
async fn main() -> Result<(), Error> {
// Connect
let client = Client::from_dsn("localhost:3141")?;
let mut conn = client.connect().await?;
// Execute query
let mut params = HashMap::new();
params.insert("age".to_string(), Value::int(30));
let (page, _) = conn
.query_with_params(
"MATCH (p:Person) WHERE p.age > $age RETURN p.name, p.city",
¶ms,
)
.await?;
// Process results
for row in &page.rows {
println!(
\"{} from {}\",
row.get(\"p.name\").unwrap().as_string()?,
row.get(\"p.city\").unwrap().as_string()?
);
}
Ok(())
}
Advanced Features:
// Transactions
use geode_client::Value;
use std::collections::HashMap;
let mut conn = client.connect().await?;
conn.begin().await?;
let mut params = HashMap::new();
params.insert("name".to_string(), Value::string("Alice"));
conn.query_with_params("CREATE (u:User {name: $name})", ¶ms).await?;
conn.commit().await?;
// Concurrent queries
use futures::future::join_all;
let queries: Vec<_> = user_ids.iter().map(|id| {
client.query(
"MATCH (u:User {id: $id}) RETURN u.name",
&[("id", id.into())]
)
}).collect();
let results = join_all(queries).await;
Development Workflows
Local Development Loop
# 1. Start Geode in one terminal
./geode serve --config config/dev.yaml
# 2. Run application in another terminal
# Go
go run main.go
# Python
python app.py
# Rust
cargo run
# 3. Use shell for ad-hoc queries
./geode shell
Hot Reload (for application code)
Go with Air:
# .air.toml
[build]
cmd = "go build -o ./tmp/main ."
bin = "tmp/main"
include_ext = ["go"]
exclude_dir = ["tmp", "vendor"]
Python with watchdog:
# run_dev.py
from watchdog.observers import Observer
from watchdog.events import FileSystemEventHandler
import subprocess
class ChangeHandler(FileSystemEventHandler):
def on_modified(self, event):
subprocess.run(["python", "app.py"])
observer = Observer()
observer.schedule(ChangeHandler(), path='.', recursive=True)
observer.start()
Testing Strategy
Unit Tests (test queries in isolation):
import pytest
from geode_client import Client
@pytest.fixture
async def client():
client = Client("localhost:3141")
async with client.connection() as c:
yield c
@pytest.mark.asyncio
async def test_create_user(client):
# Setup
await c.query("DELETE (u:User {email: '[email protected]'})")
# Execute
await c.query(
"INSERT (u:User {email: $email, name: $name})",
{"email": "[email protected]", "name": "Test User"}
)
# Verify
result, _ = await c.query(
"MATCH (u:User {email: $email}) RETURN u.name",
{"email": "[email protected]"}
)
rows = [row for row in result.rows]
assert len(rows) == 1
assert rows[0]['u.name'] == "Test User"
Integration Tests (test complete workflows):
@pytest.mark.integration
async def test_order_creation_workflow(client):
# Create test data
await tx.query(
"INSERT (u:User {id: 'test_user', name: 'Test'})"
)
await tx.query(
"INSERT (p:Product {id: 'test_prod', price: 99.99})"
)
# Execute workflow
async with client.connection() as tx:
await tx.begin()
await tx.execute(
"INSERT (o:Order {id: 'test_order', total: 99.99})"
)
await tx.execute(
"MATCH (u:User {id: 'test_user'}), (o:Order {id: 'test_order'}) "
"INSERT (u)-[:PLACED]->(o)"
)
await tx.commit()
# Verify
result, _ = await tx.query(
"MATCH (u:User {id: 'test_user'})-[:PLACED]->(o:Order) "
"RETURN o.id, o.total"
)
rows = [row for row in result.rows]
assert len(rows) == 1
assert rows[0]['o.total'] == 99.99
Load Tests (test performance):
import asyncio
from time import time
async def load_test(client, num_queries=1000):
start = time()
tasks = [
client.query("MATCH (p:Person) RETURN p.name LIMIT 1")
for _ in range(num_queries)
]
results = await asyncio.gather(*tasks)
duration = time() - start
qps = num_queries / duration
print(f"Executed {num_queries} queries in {duration:.2f}s ({qps:.0f} QPS)")
Debugging
Enable Debug Logging
Server-side:
# config/dev.yaml
server:
log_level: "debug"
Client-side (Python):
import logging
logging.basicConfig(level=logging.DEBUG)
# Client will log all queries
client = Client("localhost:3141")
Profile Queries
-- Profile query execution
PROFILE
MATCH (p:Person)-[:KNOWS*2..3]->(friend)
WHERE p.city = 'San Francisco'
RETURN friend.name;
Analyze output:
- Execution plan
- Operator costs
- Index usage
- Rows processed
Monitor Server Metrics
# Check server logs
tail -f /var/log/geode/server.log
# Monitor resource usage
docker stats geode
# Or with native installation
top -p $(pidof geode)
Common Issues
Connection refused:
# Check server is running
ps aux | grep geode
# Check port is open
netstat -an | grep 3141
# Check firewall
sudo ufw status
Slow queries:
-- Check for missing indexes
PROFILE
MATCH (p:Person {email: $email}) RETURN p;
-- If no index used, create one
CREATE INDEX person_email ON Person(email);
Out of memory:
# Increase memory limit (config/dev.yaml)
performance:
max_memory: "4GB"
query_memory_limit: "512MB"
Sample Application
Here’s a complete example - a social network API:
# app.py
from fastapi import FastAPI, HTTPException
from geode_client import Client
from pydantic import BaseModel
app = FastAPI()
client = Client("localhost:3141", pool_size=20)
class User(BaseModel):
id: str
name: str
email: str
class Post(BaseModel):
id: str
user_id: str
content: str
@app.post("/users")
async def create_user(user: User):
try:
await client.query(
"INSERT (u:User {id: $id, name: $name, email: $email})",
{"id": user.id, "name": user.name, "email": user.email}
)
return {"status": "created", "user": user}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@app.get("/users/{user_id}/friends")
async def get_friends(user_id: str):
result, _ = await client.query(
"MATCH (u:User {id: $id})-[:FRIENDS_WITH]->(friend:User) "
"RETURN friend.id, friend.name",
{"id": user_id}
)
friends = [
{"id": row['friend.id'], "name": row['friend.name']}
for row in result.rows
]
return {"friends": friends}
@app.get("/users/{user_id}/recommendations")
async def get_friend_recommendations(user_id: str):
result, _ = await client.query(
"MATCH (u:User {id: $id})-[:FRIENDS_WITH]->(f1)-[:FRIENDS_WITH]->(f2) "
"WHERE f2 <> u AND NOT EXISTS { MATCH (u)-[:FRIENDS_WITH]->(f2) } "
"RETURN f2.id, f2.name, count(f1) AS mutual_friends "
"ORDER BY mutual_friends DESC "
"LIMIT 10",
{"id": user_id}
)
recommendations = [
{
"id": row['f2.id'],
"name": row['f2.name'],
"mutual_friends": row['mutual_friends']
}
for row in result.rows
]
return {"recommendations": recommendations}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
Run:
# Install dependencies
pip install fastapi uvicorn geode-client
# Run application
python app.py
Test:
# Create users
curl -X POST http://localhost:8000/users \
-H "Content-Type: application/json" \
-d '{"id": "u1", "name": "Alice", "email": "[email protected]"}'
# Get friend recommendations
curl http://localhost:8000/users/u1/recommendations
Contributing to Geode
Development Setup
# Fork and clone
git clone https://gitlab.com/YOUR_USERNAME/geode.git
cd geode
# Build
make build
# Run tests
make test
# Run comprehensive test suite
make geodetestlab-comprehensive
Code Style
Follow existing patterns:
- Zig: Standard Zig formatting (
zig fmt) - Go:
gofmtandgolint - Python:
blackandpylint - Rust:
rustfmtandclippy
Submitting Changes
# Create feature branch
git checkout -b feature/my-feature
# Make changes, commit
git add .
git commit -m "feat: Add new feature"
# Push
git push origin feature/my-feature
# Create merge request on GitLab
Resources
Documentation
- API Reference: https://geodedb.com/docs/api/
- GQL Syntax: https://geodedb.com/docs/gql/
- Client Libraries: https://geodedb.com/docs/clients/
Community
- Forum: https://forum.geodedb.com
- Chat: https://discord.gg/geodedb
- Issue Tracker: https://gitlab.com/devnw/codepros/geode/issues
Examples
- Sample Applications: https://github.com/geodedb/examples
- Client Examples: Each client repo includes examples/
- Tutorial Series: https://geodedb.com/tutorials/
Conclusion
This guide provides the foundation for developing applications with Geode. Key takeaways:
- Setup: Docker for quick start, build from source for development
- Client Libraries: Choose Go, Python, Rust, or Zig based on your stack
- Testing: Comprehensive unit, integration, and load tests
- Debugging: Use PROFILE, logs, and metrics to diagnose issues
- Best Practices: Connection pooling, parameterized queries, proper error handling
Explore the documentation for advanced topics, optimization techniques, and deployment strategies. Happy coding!