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
/sqlpath 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:
createClientfrom@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)
})- 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
wherefiltering 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
- Caching: Use React Query for SQL approach, GraphQL client cache for GraphQL
- Pagination: Choose appropriate strategy based on data volume
- Filtering: Implement server-side filtering when possible
- 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.