Skip to content

Ponder Client Integration Guide

Overview

Ponder provides powerful client packages for integrating blockchain data into your applications. This guide covers both SQL/Drizzle and GraphQL approaches, helping you choose the right method for your use case.

Client Packages

@ponder/client

The foundational client package for direct Ponder integration.

  • Purpose: Backend and frontend combined client
  • Transport: HTTP requests to /sql path on indexer API
  • Focus: Internal usage scenarios
  • Benefits: Direct access to Ponder's SQL interface

@ponder/react

React-specific integration layer that combines multiple technologies.

  • Dependencies:
    • createClient from @ponder/client
    • TanStack React Query for state management
    • Drizzle queries for type-safe database operations
  • Benefits: Flexible connection with direct Drizzle querying
  • Use Case: React applications requiring real-time blockchain data

Client Configuration

Basic Setup

import { createClient } from '@ponder/client'
import { schema } from '../../indexer/ponder.schema'
 
// Create client with URL and schema
const client = createClient("https://your-ponder-url/sql", { schema })

Monorepo Integration

Thanks to our monorepo structure, schemas can be easily shared:

// Direct schema import from indexer
import { roleActivities, users, roleStats } from '../../indexer/ponder.schema'
 
const client = createClient(ponderUrl, { 
  schema: { roleActivities, users, roleStats } 
})

Integration Approaches

1. SQL/Drizzle Approach (Recommended for Internal Use)

Best for: Internal applications, type-safe queries, familiar SQL patterns

import { usePonderQuery } from '@ponder/react'
import { roleActivities } from '../../indexer/ponder.schema'
 
// Type-safe Drizzle queries
const { data, isLoading } = usePonderQuery({
  queryFn: (db) => 
    db.select()
      .from(roleActivities)
      .where(eq(roleActivities.account, userAddress))
      .orderBy(desc(roleActivities.timestamp))
      .limit(20)
})
Benefits:
  • Type-safe queries with full IDE support
  • Familiar SQL patterns and operators
  • Direct schema integration
  • Standard limit/offset pagination
  • Efficient for simple to moderate complexity queries

2. GraphQL Approach (Secure External Integration)

Best for: External integrations, enhanced security, third-party applications

Basic GraphQL Query

query RoleActivities {
  roleActivitiess {
    items {
      id
      type
      role
      account
      sender
      timestamp
      blockNumber
    }
    totalCount
    pageInfo {
      startCursor
      endCursor
      hasNextPage
      hasPreviousPage
    }
  }
}

Cursor-Based Pagination

Ponder uses cursor-based pagination for GraphQL:

# First page
query FirstPage {
  roleActivitiess(first: 10) {
    items { /* ... */ }
    pageInfo {
      endCursor
      hasNextPage
    }
  }
}
 
# Next page using cursor
query NextPage {
  roleActivitiess(first: 10, after: "cursor_value") {
    items { /* ... */ }
    pageInfo {
      endCursor
      hasNextPage
    }
  }
}

Implementation Patterns

Infinite Query with GraphQL

import { useInfiniteQuery } from '@tanstack/react-query'
import { graphqlClient } from './graphql-client'
 
const useInfiniteActivities = () => {
  return useInfiniteQuery({
    queryKey: ['roleActivities'],
    queryFn: ({ pageParam }) => 
      graphqlClient.request(ROLE_ACTIVITIES_QUERY, { 
        after: pageParam 
      }),
    getNextPageParam: (lastPage) => 
      lastPage.roleActivitiess.pageInfo.hasNextPage 
        ? lastPage.roleActivitiess.pageInfo.endCursor 
        : undefined,
  })
}

Full Data Fetch + Table Integration

// Fetch all data for client-side table operations
const fetchAllActivities = async () => {
  let allData = []
  let hasNextPage = true
  let cursor = null
  
  while (hasNextPage) {
    const result = await graphqlClient.request(query, { after: cursor })
    allData.push(...result.roleActivitiess.items)
    hasNextPage = result.roleActivitiess.pageInfo.hasNextPage
    cursor = result.roleActivitiess.pageInfo.endCursor
  }
  
  return allData
}
 
// Use with TanStack Table for advanced features
const table = useReactTable({
  data: allData,
  columns,
  getCoreRowModel: getCoreRowModel(),
  getPaginationRowModel: getPaginationRowModel(),
  getSortedRowModel: getSortedRowModel(),
  getFilteredRowModel: getFilteredRowModel(),
})

Advanced Usage

Complex Filtering

SQL/Drizzle Filtering

const { data } = usePonderQuery({
  queryFn: (db) => 
    db.select()
      .from(roleActivities)
      .where(
        and(
          eq(roleActivities.type, 0), // GRANTED
          gte(roleActivities.timestamp, startTime),
          lte(roleActivities.timestamp, endTime)
        )
      )
      .orderBy(desc(roleActivities.timestamp))
})

GraphQL Filtering

query FilteredActivities($where: RoleActivitiesWhereInput) {
  roleActivitiess(where: $where) {
    items {
      id
      type
      account
      timestamp
    }
  }
}

Note: GraphQL where filtering requires careful implementation and may need manual customization based on your specific filtering requirements.

Real-time Updates

// React Query with polling for real-time updates
const { data } = usePonderQuery({
  queryFn: (db) => db.select().from(roleActivities).limit(10),
  refetchInterval: 5000, // Poll every 5 seconds
})

URL Patterns

  • SQL/Drizzle: https://your-ponder-url/sql
  • GraphQL: https://your-ponder-url (no additional path)

Best Practices

When to Use SQL/Drizzle

  • Internal applications with full control
  • Need for type-safe queries
  • Familiar with SQL patterns
  • Monorepo structure available
  • Simple to moderate pagination needs

When to Use GraphQL

  • External integrations or public APIs
  • Enhanced security requirements
  • Third-party application integration
  • Complex data relationships
  • Large datasets requiring cursor pagination

Performance Considerations

  1. Caching: Use React Query for SQL approach, GraphQL client cache for GraphQL
  2. Pagination: Choose appropriate strategy based on data volume
  3. Filtering: Implement server-side filtering when possible
  4. Real-time: Balance update frequency with performance needs

Error Handling

// SQL/Drizzle error handling
const { data, error, isError } = usePonderQuery({
  queryFn: (db) => db.select().from(roleActivities),
  retry: 3,
  onError: (error) => {
    console.error('Query failed:', error)
    // Handle error appropriately
  }
})
 
// GraphQL error handling
const { data, error } = useQuery({
  queryKey: ['activities'],
  queryFn: () => graphqlClient.request(query),
  onError: (error) => {
    if (error.response?.errors) {
      // Handle GraphQL errors
    }
  }
})

This dual-approach architecture provides flexibility for different integration needs while maintaining type safety and performance optimization opportunities.