Client queries
@ponder/client
and @ponder/react
are TypeScript client packages for querying a Ponder app with end-to-end type safety and live updates.
Enable on the server
Client queries are available starting from version 0.9.0
. Read the
migration guide for more details.
Register the client
middleware on your Ponder server to enable client queries.
import { db } from "ponder:api";
import { Hono } from "hono";
import { client } from "ponder";
const app = new Hono();
app.use(client({ db }));
export default app;
@ponder/client
The @ponder/client
package works in any JavaScript environment, including the browser, server-side scripts, and both client and server code from web frameworks like Next.js. If you're using a React framework, use the @ponder/react
package instead.
Guide
Installation
Install the @ponder/client
package in your client project.
pnpm add @ponder/client
Setup client
Create a client using the URL of your Ponder server. Import your Ponder schema into the same file.
import { createClient } from "@ponder/client";
import * as schema from "../../ponder/ponder.schema";
const client = createClient("http://localhost:42069", { schema });
export { client, schema };
Run a query
Use the client.db
method to execute a SELECT
statement using Drizzle. The query builder is fully type-safe to provide static query validation and inferred result types.
import { createClient } from "@ponder/client";
import * as schema from "../../ponder/ponder.schema";
const client = createClient("https://...", { schema });
const result = await client.db.select().from(schema.account).limit(10);
API Reference
client.db
This method uses a readonly database role, with strict query limits. It provides a Drizzle query builder, similar to API functions.
import { createClient } from "@ponder/client";
import * as schema from "../../ponder/ponder.schema";
const client = createClient("https://...", { schema });
const result = await client.db.select().from(schema.account).limit(10);
client.live
Subscribe to live updates.
When you initiate a live query, the client opens a persistent HTTP connection with the server using Server-Sent Events (SSE). Then, the server notifies the client whenever a new block gets indexed. If a query result is no longer valid, the client will immediately refetch it to receive the latest result. This approach achieves low-latency updates with minimal network traffic.
To avoid browser quotas, each client
instance uses at most one SSE connection at a time.
import { createClient } from "@ponder/client";
import * as schema from "../../ponder/ponder.schema";
const client = createClient("https://...", { schema });
const { unsubscribe } = client.live(
(db) => db.select().from(schema.account),
(result) => {
// ...
},
(error) => {
// ...
}
);
Status
The status
object is a Drizzle table object which contains information about the indexing status of each chain. You can query it just like any other table exported by ponder.schema.ts
.
import { createClient, status } from "@ponder/client";
const client = createClient("https://...");
const mainnetStatus = client.db
.select()
.from(status)
.where(eq(status.chainId, 1));
Query from React
The @ponder/react
package includes React hooks for subscribing to live updates from your database. It uses @ponder/client
and Tanstack Query under the hood.
import { usePonderQuery } from "@ponder/react";
const { data, isError, isPending } = usePonderQuery({
queryFn: (db) => db.select().from(schema.depositEvent).limit(10),
});
usePonderQuery
uses TanStack Query for async state management. queryFn
is overriden to include a database object, similar to the client.db
method, and queryKey
is automatically generated. All other options are preserved.
Guide
Installation
Install @ponder/react
and peer dependencies
pnpm add @ponder/react @ponder/client @tanstack/react-query
Create client
Create a client using the URL of your Ponder server. Import your Ponder schema into the same file.
import { createClient } from "@ponder/client";
import * as schema from "../../ponder/ponder.schema";
export const client = createClient("http://localhost:42069", { schema });
Wrap app in provider
Wrap your app with the PonderProvider
React Context Provider and include the client object you just created.
import { PonderProvider } from '@ponder/react'
import { client } from './lib/ponder'
function App() {
return (
<PonderProvider client={client}>
{/** ... */}
</PonderProvider>
)
}
Setup TanStack Query
Inside the PonderProvider
, wrap your app in a TanStack Query React Context Provider. Read more about setting up TanStack Query.
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { PonderProvider } from '@ponder/react'
import { client } from './lib/ponder'
const queryClient = new QueryClient()
function App() {
return (
<PonderProvider client={client}>
<QueryClientProvider client={queryClient}>
{/** ... */}
</QueryClientProvider>
</PonderProvider>
)
}
Use the hook
import { usePonderQuery } from '@ponder/react'
import { schema } from '../lib/ponder'
export function Deposits() {
const { data, isError, isPending } = usePonderQuery({
queryFn: (db) =>
db.select()
.from(schema.depositEvent)
.orderBy(schema.depositEvent.timestamp)
.limit(10),
staleTime: Number.POSITIVE_INFINITY,
});
if (isPending) return <div>Loading deposits</div>
if (isError) return <div>Error fetching deposits</div>
return <div>Deposits: {data}</div>
}
Example projects
These example apps demonstrate how to use @ponder/client
.