Dubhe GraphQL Server
The Dubhe GraphQL Server is a universal server adapter that automatically connects to databases created by sui-rust-indexer
and dynamically generates complete GraphQL APIs. It provides advanced filtering, real-time subscriptions, and comprehensive data access capabilities.
Installation
pnpm install @0xobelisk/graphql-server
Or install globally:
npm install -g @0xobelisk/graphql-server
Quick Start
Using the CLI
# Start the server with default configuration
dubhe-graphql-server start
# Start with custom configuration
dubhe-graphql-server start --port 4000 --database-url postgres://user:pass@localhost:5432/db
Environment Configuration
Create a .env
file in your project root:
# Database configuration (connect to sui-rust-indexer database)
DATABASE_URL=postgres://username:password@localhost:5432/sui_indexer_db
# Server configuration
PORT=4000
NODE_ENV=development
# GraphQL configuration
GRAPHQL_ENDPOINT=/graphql
PG_SCHEMA=public
# Feature toggles
ENABLE_CORS=true
ENABLE_SUBSCRIPTIONS=true
# Performance settings
QUERY_TIMEOUT=30000
MAX_CONNECTIONS=20
HEARTBEAT_INTERVAL=30000
# Subscription capabilities
ENABLE_LIVE_QUERIES=true
ENABLE_PG_SUBSCRIPTIONS=true
ENABLE_NATIVE_WEBSOCKET=true
REALTIME_PORT=4001
Core Features
Intelligent Database Adaptation
The server automatically scans and adapts to your database structure:
- Dynamic Scanning: Automatically discovers all tables created by
sui-rust-indexer
- PostGraphile Powered: Generates GraphQL APIs based on database schema
- Zero Configuration: No manual schema definition required
Advanced Filtering
Supports 20+ filtering operators:
# Basic filtering
query GetHighValueAccounts {
storeAccounts(filter: {
balance: { gt: "1000" }
}) {
nodes {
assetId
account
balance
}
}
}
# Complex logical combinations
query GetComplexFilteredAccounts {
storeAccounts(filter: {
and: [
{
or: [
{ balance: { gt: "1000" } },
{ assetId: { like: "%special%" } }
]
},
{
not: {
account: { includesInsensitive: "test" }
}
}
]
}) {
nodes {
assetId
account
balance
}
}
}
Real-time Subscriptions
Built-in WebSocket support for real-time data:
# Subscribe to schema changes
subscription OnSchemaChanges {
allDubheStoreSchemas(first: 1, orderBy: [CREATED_AT_DESC]) {
nodes {
id
name
value
createdAt
}
}
}
# Subscribe to new events
subscription OnNewEvents {
allDubheStoreEvents(first: 1, orderBy: [CREATED_AT_DESC]) {
nodes {
id
name
value
checkpoint
}
}
}
Deployment
Docker Deployment
Create a docker-compose.yml
:
version: '3.8'
services:
graphql-server:
image: dubhe-graphql-server
ports:
- "4000:4000"
environment:
- DATABASE_URL=postgres://user:password@postgres:5432/sui_indexer
- PORT=4000
- ENABLE_SUBSCRIPTIONS=true
depends_on:
- postgres
postgres:
image: postgres:15
environment:
- POSTGRES_DB=sui_indexer
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
volumes:
- postgres_data:/var/lib/postgresql/data
volumes:
postgres_data:
Run with:
docker-compose up -d
Manual Deployment
# Build the project
pnpm build
# Set environment variables
export DATABASE_URL="postgres://user:password@localhost:5432/sui_indexer"
export PORT=4000
export ENABLE_SUBSCRIPTIONS=true
# Start the server
pnpm start
Production Configuration
NODE_ENV=production
DATABASE_URL=postgres://user:password@prod-db:5432/sui_indexer
PORT=4000
# Security
ENABLE_CORS=false
CORS_ORIGIN=https://yourdomain.com
# Performance
MAX_CONNECTIONS=50
QUERY_TIMEOUT=60000
HEARTBEAT_INTERVAL=30000
# Monitoring
ENABLE_METRICS=true
LOG_LEVEL=info
API Usage
System Tables
Query built-in system tables:
# Query Schemas table
query GetSchemas {
allDubheStoreSchemas(first: 10) {
nodes {
id
name
key1
key2
value
lastUpdateCheckpoint
isRemoved
createdAt
}
}
}
# Query Transactions table
query GetTransactions {
allDubheStoreTransactions(first: 10) {
nodes {
id
sender
checkpoint
digest
package
module
function
arguments
createdAt
}
}
}
# Query Events table
query GetEvents {
allDubheStoreEvents(first: 10) {
nodes {
id
sender
name
value
checkpoint
digest
createdAt
}
}
}
Dynamic Tables
Query dynamically created tables from your contract configuration:
# Query store_accounts table (if exists)
query GetAccounts {
allStoreAccounts {
nodes {
assetId
account
balance
}
}
}
# Query store_position table (if exists)
query GetPositions {
allStorePositions {
nodes {
player
x
y
}
}
}
Pagination
# Paginated query
query GetSchemasPaginated($after: Cursor) {
allDubheStoreSchemas(first: 10, after: $after) {
pageInfo {
hasNextPage
endCursor
}
nodes {
id
name
value
}
}
}
Filtering and Sorting
# Conditional filtering
query GetSchemasByName($name: String!) {
allDubheStoreSchemas(condition: { name: $name }) {
nodes {
id
name
key1
key2
value
}
}
}
# Sorted query
query GetRecentTransactions {
allDubheStoreTransactions(
first: 20,
orderBy: [CREATED_AT_DESC]
) {
nodes {
id
sender
function
checkpoint
createdAt
}
}
}
Configuration
Server Configuration
interface ServerConfig {
port: string;
databaseUrl: string;
schema: string;
endpoint: string;
cors: boolean;
subscriptions: boolean;
env: string;
debug: boolean;
queryTimeout: number;
maxConnections: number;
heartbeatInterval: number;
enableMetrics: boolean;
enableLiveQueries: boolean;
enablePgSubscriptions: boolean;
enableNativeWebSocket: boolean;
realtimePort?: number;
debugNotifications: boolean;
}
Database Permissions
Set up proper database permissions:
-- Create read-only user
CREATE USER graphql_readonly WITH PASSWORD 'secure_password';
-- Grant query permissions
GRANT CONNECT ON DATABASE sui_indexer TO graphql_readonly;
GRANT USAGE ON SCHEMA public TO graphql_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO graphql_readonly;
-- If write permissions needed
GRANT INSERT, UPDATE, DELETE ON specific_tables TO graphql_readonly;
Monitoring and Debugging
Health Checks
The server provides several monitoring endpoints:
http://localhost:4000/
- Welcome page with system informationhttp://localhost:4000/health
- Health check endpointhttp://localhost:4000/subscription-config
- Subscription configurationhttp://localhost:4000/subscription-docs
- Configuration documentation
Debug Mode
Enable debug mode for detailed logging:
DEBUG=postgraphile:* dubhe-graphql-server start --debug
Performance Monitoring
ENABLE_METRICS=true
LOG_LEVEL=debug
Integration
Frontend Integration
Apollo Client Setup
import { ApolloClient, InMemoryCache, split, HttpLink } from '@apollo/client';
import { GraphQLWsLink } from '@apollo/client/link/subscriptions';
import { createClient } from 'graphql-ws';
const httpLink = new HttpLink({
uri: 'http://localhost:4000/graphql',
});
const wsLink = new GraphQLWsLink(createClient({
url: 'ws://localhost:4000/graphql',
}));
const splitLink = split(
({ query }) => {
const definition = getMainDefinition(query);
return (
definition.kind === 'OperationDefinition' &&
definition.operation === 'subscription'
);
},
wsLink,
httpLink,
);
const client = new ApolloClient({
link: splitLink,
cache: new InMemoryCache(),
});
React Integration
import { useQuery, useSubscription } from '@apollo/client';
import { gql } from '@apollo/client';
const GET_ACCOUNTS = gql`
query GetAccounts($first: Int!) {
allStoreAccounts(first: $first) {
nodes {
assetId
account
balance
}
}
}
`;
const ACCOUNT_SUBSCRIPTION = gql`
subscription OnAccountChanges {
allStoreAccounts(first: 10, orderBy: [UPDATED_AT_DESC]) {
nodes {
assetId
account
balance
}
}
}
`;
function AccountList() {
const { data, loading, error } = useQuery(GET_ACCOUNTS, {
variables: { first: 10 },
});
useSubscription(ACCOUNT_SUBSCRIPTION, {
onSubscriptionData: ({ subscriptionData }) => {
console.log('Account updated:', subscriptionData.data);
},
});
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{data.allStoreAccounts.nodes.map(account => (
<div key={account.assetId}>
{account.account}: {account.balance}
</div>
))}
</div>
);
}
Backend Integration
// Node.js integration
import { ApolloClient, InMemoryCache, createHttpLink } from '@apollo/client/core';
import fetch from 'cross-fetch';
const client = new ApolloClient({
link: createHttpLink({
uri: 'http://localhost:4000/graphql',
fetch,
}),
cache: new InMemoryCache(),
});
// Query data
const result = await client.query({
query: gql`
query GetTransactions {
allDubheStoreTransactions(first: 10) {
nodes {
id
sender
digest
}
}
}
`,
});
Troubleshooting
Common Issues
-
Database Connection Failed
Solution: Check DATABASE_URL and database service status
-
Table Scan Empty
Solution: Ensure sui-rust-indexer is running and has created tables
-
Schema Generation Failed
Solution: Check if table_fields table exists and has data
-
WebSocket Connection Failed
Solution: Check firewall settings and ENABLE_SUBSCRIPTIONS configuration
Debug Commands
# View generated schema
ls -la *.graphql
# Check database connection
psql $DATABASE_URL -c "SELECT version();"
# Test GraphQL endpoint
curl -X POST http://localhost:4000/graphql \
-H "Content-Type: application/json" \
-d '{"query": "{ __schema { types { name } } }"}'
Architecture
How It Works
sui-rust-indexer database
↓
[Database Introspector]
↓
[PostGraphile]
↓
[GraphQL API]
↓
[WebSocket]
- Database Scanning: Automatically scans all tables at startup
- Structure Parsing: Reads dynamic table structures from metadata
- Schema Generation: PostGraphile generates GraphQL schema
- API Service: Provides complete GraphQL CRUD operations
Supported Table Types
-
System Tables:
__dubheStoreTransactions
- Transaction records__dubheStoreSchemas
- Schema data__dubheStoreEvents
- Event recordstable_fields
- Table structure metadata
-
Dynamic Tables:
store_*
- Tables dynamically created based on configuration
Best Practices
- Use connection pooling for production deployments
- Configure proper database permissions for security
- Enable CORS carefully in production
- Monitor subscription connections to prevent resource exhaustion
- Use read replicas for heavy query workloads
- Implement rate limiting for public APIs
- Set up proper logging for debugging and monitoring
- Use environment-specific configurations for different deployment stages