Client queries
The @ponder/client
package is a TypeScript client for querying a Ponder app with end-to-end type safety.
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;
Query from React
The @ponder/react
package includes React hooks for subscribing to live updates from your database.
For an example of how to use @ponder/client
with @tanstack/react-query
for
an automatically updating React hook, see the Next.js
example.
Installation
Install @ponder/react
and @tanstack/react-query
in your React project.
pnpm add @ponder/react @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(desc(schema.depositEvent.timestamp))
.limit(10),
});
if (isPending) return <div>Loading deposits</div>
if (isError) return <div>Error fetching deposits</div>
return <div>Deposits: {data}</div>
}
Query from Node.js
Install @ponder/client
Install the @ponder/client
package in your client project. This package works in any JavaScript environment, including the browser, server-side scripts, and both client and server code from web frameworks like Next.js.
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
table contains information about the indexing status of each chain.
import { createClient, status } from "@ponder/client";
const client = createClient("https://...");
const mainnetStatus = client.db
.select()
.from(status)
.where(eq(status.chainId, 1));
Example projects
These example apps demonstrate how to use @ponder/client
.