API Development
API development encompasses the design, implementation, and documentation of interfaces for accessing Geode’s graph database functionality. Well-designed APIs enable developers to build powerful applications while maintaining security, performance, and backward compatibility.
Geode Client Libraries
Native Protocol
Geode’s native protocol uses QUIC over TLS for efficient, secure communication:
Transport - QUIC (UDP-based, multiplexed) Security - TLS 1.3 required Format - Protobuf wire protocol Port - 3141 (standard) or 8443 (alternative)
Client Message Format:
{"type": "RUN_GQL", "query": "MATCH (n) RETURN n LIMIT 10"}
Server Response Format:
{"type": "SCHEMA", "columns": ["n"]}
{"type": "BINDINGS", "row": [{"id": "1", "labels": ["Person"]}]}
{"type": "BINDINGS", "row": [{"id": "2", "labels": ["Person"]}]}
Go Client Library
Full-featured Go client with database/sql compatibility:
import (
"database/sql"
_ "geodedb.com/geode"
)
// Open connection
db, err := sql.Open("geode", "quic://localhost:3141?tls=true")
if err != nil {
log.Fatal(err)
}
defer db.Close()
// Execute query
rows, err := db.Query("MATCH (p:Person) WHERE p.age > ? RETURN p", 18)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// Process results
for rows.Next() {
var person Person
if err := rows.Scan(&person); err != nil {
log.Fatal(err)
}
fmt.Printf("Person: %+v\n", person)
}
Features:
- Connection pooling
- Prepared statements
- Transaction support
- Context-aware operations
- Type-safe query builders
Python Client Library
Async Python client built on aioquic:
import asyncio
from geode_client import Client
async def main():
# Connect to database
client = Client("localhost", 3141, use_tls=True)
async with client.connection() as client:
# Execute query
result, _ = await client.query(
"MATCH (p:Person) WHERE p.age > $age RETURN p",
{"age": 18}
)
# Process results
for row in result.rows:
print(f"Person: {row['p']}")
asyncio.run(main())
Features:
- Full async/await support
- Connection pooling (workload-dependent throughput)
- Query builders
- Row-Level Security policy management
- Savepoint support
Rust Client Library
High-performance Rust client with tokio:
use geode_client::{Client, Query};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect to database
let client = Client::connect("localhost:3141").await?;
// Execute query
let query = Query::new("MATCH (p:Person) WHERE p.age > $age RETURN p")
.param("age", 18);
let mut result = client.execute(query).await?;
// Process results
while let Some(row) = result.next().await? {
println!("Person: {:?}", row.get::<Person>("p")?);
}
Ok(())
}
Features:
- Zero-cost abstractions
- Type-safe query builders
- Connection pooling (workload-dependent throughput)
- Transaction support
- Compile-time query validation
Zig Client Library
Native Zig client for systems programming:
const geode = @import("geode");
pub fn main() !void {
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
defer _ = gpa.deinit();
// Connect to database
var client = try geode.Client.connect(
gpa.allocator(),
"localhost",
3141,
.{ .tls = true }
);
defer client.deinit();
// Execute query
var result = try client.execute(
"MATCH (p:Person) WHERE p.age > $age RETURN p",
.{ .age = 18 }
);
defer result.deinit();
// Process results
while (try result.next()) |row| {
const person = try row.get("p", Person);
std.debug.print("Person: {}\n", .{person});
}
}
REST API Design
RESTful Endpoints
Design clean, resource-oriented APIs:
GET /api/v1/graphs # List graphs
POST /api/v1/graphs # Create graph
GET /api/v1/graphs/{id} # Get graph details
DELETE /api/v1/graphs/{id} # Delete graph
GET /api/v1/graphs/{id}/nodes # List nodes
POST /api/v1/graphs/{id}/nodes # Create node
GET /api/v1/graphs/{id}/nodes/{nid} # Get node
PUT /api/v1/graphs/{id}/nodes/{nid} # Update node
DELETE /api/v1/graphs/{id}/nodes/{nid} # Delete node
POST /api/v1/graphs/{id}/query # Execute GQL query
Request/Response Format
Use consistent JSON format:
// Request
POST /api/v1/graphs/social/query
{
"query": "MATCH (p:Person) WHERE p.age > $age RETURN p",
"parameters": {
"age": 18
},
"consistency": "strong"
}
// Response
{
"status": "success",
"schema": {
"columns": [
{"name": "p", "type": "node"}
]
},
"data": [
{
"p": {
"id": "person:123",
"labels": ["Person"],
"properties": {
"name": "Alice",
"age": 25
}
}
}
],
"metadata": {
"rows": 1,
"execution_time_ms": 12,
"cached": false
}
}
Error Handling
Return meaningful error responses:
// Error Response
{
"status": "error",
"error": {
"code": "INVALID_QUERY",
"message": "Syntax error at line 1, column 15",
"details": {
"line": 1,
"column": 15,
"expected": "WHERE or RETURN",
"got": "FROM"
}
},
"request_id": "req_abc123"
}
Standard error codes:
- 400 - Bad Request (invalid syntax)
- 401 - Unauthorized (authentication required)
- 403 - Forbidden (insufficient permissions)
- 404 - Not Found (resource doesn’t exist)
- 409 - Conflict (constraint violation)
- 429 - Too Many Requests (rate limited)
- 500 - Internal Server Error
GraphQL API
Schema Definition
Define GraphQL schema for graph access:
type Person {
id: ID!
name: String!
age: Int!
friends: [Person!]!
posts: [Post!]!
}
type Post {
id: ID!
title: String!
content: String!
author: Person!
comments: [Comment!]!
}
type Query {
person(id: ID!): Person
people(filter: PersonFilter, limit: Int): [Person!]!
post(id: ID!): Post
posts(filter: PostFilter, limit: Int): [Post!]!
}
type Mutation {
createPerson(input: CreatePersonInput!): Person!
updatePerson(id: ID!, input: UpdatePersonInput!): Person!
deletePerson(id: ID!): Boolean!
createPost(input: CreatePostInput!): Post!
}
input PersonFilter {
minAge: Int
maxAge: Int
name: String
}
Resolver Implementation
Map GraphQL to GQL queries:
const resolvers = {
Query: {
person: async (_, { id }, { geode }) => {
const result = await geode.execute(
'MATCH (p:Person {id: $id}) RETURN p',
{ id }
);
return result.rows[0]?.p;
},
people: async (_, { filter, limit = 100 }, { geode }) => {
let query = 'MATCH (p:Person)';
const params = {};
if (filter?.minAge) {
query += ' WHERE p.age >= $minAge';
params.minAge = filter.minAge;
}
query += ' RETURN p LIMIT $limit';
params.limit = limit;
const result = await geode.execute(query, params);
return result.rows.map(row => row.p);
}
},
Person: {
friends: async (person, _, { geode }) => {
const result = await geode.execute(
'MATCH (p:Person {id: $id})-[:FRIEND]->(f:Person) RETURN f',
{ id: person.id }
);
return result.rows.map(row => row.f);
}
}
};
Query Optimization
Optimize GraphQL queries:
DataLoader - Batch and cache database requests Query Depth Limiting - Prevent excessive nesting Complexity Analysis - Estimate query cost Persisted Queries - Pre-approve query patterns
API Versioning
Versioning Strategies
Support multiple API versions:
URL Versioning - /api/v1/, /api/v2/
Header Versioning - Accept: application/vnd.geode.v2+json
Content Negotiation - Different formats per version
// URL versioning (recommended)
GET /api/v1/graphs/social/nodes
GET /api/v2/graphs/social/nodes
// Header versioning
GET /api/graphs/social/nodes
Accept: application/vnd.geode.v2+json
// Query parameter versioning
GET /api/graphs/social/nodes?version=2
Deprecation Policy
Gracefully deprecate old versions:
- Announce - Notify users of deprecation timeline
- Warn - Include deprecation headers in responses
- Support - Maintain old version for grace period (6-12 months)
- Sunset - Remove support with final notice
Deprecation: true
Sunset: Sat, 31 Dec 2026 23:59:59 GMT
Link: <https://docs.geode.io/api/v3>; rel="successor-version"
Rate Limiting
Rate Limit Configuration
Protect API from abuse:
rate_limiting:
enabled: true
strategies:
- name: per_user
limit: 1000
window: 1h
- name: per_ip
limit: 100
window: 1m
- name: burst
limit: 50
window: 1s
Rate Limit Headers
Communicate limits to clients:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 742
X-RateLimit-Reset: 1640995200
Retry-After: 3600
Rate Limit Response
Return clear error when limited:
{
"status": "error",
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry after 3600 seconds.",
"limit": 1000,
"window": "1h",
"reset_at": "2026-01-24T12:00:00Z"
}
}
Authentication and Authorization
API Key Authentication
Simple authentication for service accounts:
# Include API key in request
curl -H "X-API-Key: gd_live_abc123..." \
https://api.geode.io/v1/graphs
OAuth 2.0
Standard OAuth flow for user authorization:
1. Client redirects to authorization endpoint
GET /oauth/authorize?client_id=...&response_type=code
2. User authenticates and grants permission
3. Client receives authorization code
GET /callback?code=abc123
4. Client exchanges code for access token
POST /oauth/token
{ "code": "abc123", "grant_type": "authorization_code" }
5. Client uses access token
GET /api/v1/graphs
Authorization: Bearer eyJhbGc...
JWT Tokens
Use JWT for stateless authentication:
// Verify JWT token
const jwt = require('jsonwebtoken');
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (err) {
res.status(401).json({ error: 'Invalid token' });
}
}
API Documentation
OpenAPI Specification
Document REST APIs with OpenAPI:
openapi: 3.0.0
info:
title: Geode Graph Database API
version: 1.0.0
description: RESTful API for Geode graph database
paths:
/graphs/{graphId}/query:
post:
summary: Execute GQL query
parameters:
- name: graphId
in: path
required: true
schema:
type: string
requestBody:
required: true
content:
application/json:
schema:
type: object
properties:
query:
type: string
parameters:
type: object
responses:
'200':
description: Query results
content:
application/json:
schema:
$ref: '#/components/schemas/QueryResult'
Interactive Documentation
Provide interactive API exploration:
Swagger UI - Browse and test REST endpoints GraphiQL - Interactive GraphQL IDE Postman Collections - Pre-built API requests
SDK Documentation
Generate client library documentation:
# Generate Go documentation
godoc -http=:6060
# Generate Python documentation
pdoc --html geode_client
# Generate Rust documentation
cargo doc --open
Best Practices
API Design Principles
Follow REST best practices:
- Use nouns for resources, verbs for actions
- Return appropriate HTTP status codes
- Support filtering, sorting, and pagination
- Version APIs from the start
- Use HTTPS for all endpoints
Performance Optimization
Optimize API performance:
Caching - Cache GET responses with ETags Compression - Enable gzip/brotli compression Connection Pooling - Reuse database connections Pagination - Limit result set sizes Field Selection - Allow clients to specify needed fields
Error Handling
Provide helpful error messages:
- Include error codes for programmatic handling
- Provide human-readable messages
- Include request IDs for debugging
- Suggest corrective actions
- Log errors for analysis
Security Considerations
Secure API endpoints:
- Require authentication for all endpoints
- Use HTTPS exclusively
- Validate and sanitize inputs
- Implement rate limiting
- Audit API access
- Rotate credentials regularly
Testing APIs
Unit Testing
Test API handlers in isolation:
describe('POST /graphs/{id}/query', () => {
it('executes valid GQL query', async () => {
const response = await request(app)
.post('/api/v1/graphs/social/query')
.send({
query: 'MATCH (n) RETURN n LIMIT 1'
});
expect(response.status).toBe(200);
expect(response.body.status).toBe('success');
});
it('returns error for invalid query', async () => {
const response = await request(app)
.post('/api/v1/graphs/social/query')
.send({
query: 'INVALID QUERY'
});
expect(response.status).toBe(400);
expect(response.body.error.code).toBe('INVALID_QUERY');
});
});
Integration Testing
Test end-to-end API flows:
def test_user_workflow():
# Create user
response = client.post('/api/v1/users', json={
'name': 'Alice',
'email': '[email protected]'
})
assert response.status_code == 201
user_id = response.json()['id']
# Get user
response = client.get(f'/api/v1/users/{user_id}')
assert response.status_code == 200
assert response.json()['name'] == 'Alice'
# Delete user
response = client.delete(f'/api/v1/users/{user_id}')
assert response.status_code == 204
Contract Testing
Verify API contracts:
# Pact contract test
interactions:
- description: Get person by ID
request:
method: GET
path: /api/v1/persons/123
response:
status: 200
body:
id: 123
name: string
age: integer
Related Topics
- Client Libraries - Native client implementations
- Security - API security best practices
- Performance - API optimization techniques
- REST - RESTful API design