API functions
API functions are user-defined TypeScript functions that handle web requests. You can use them to customize the API layer of your app with complex SQL queries, authentication, data from external sources, and more.
API functions are built on top of Hono, a fast and lightweight routing framework.
Get started
Upgrade to >=0.5.0
API functions are available starting from version 0.5.0
. Read the migration guide for more details.
Create src/api/index.ts
file
To enable API functions, create a file named src/api/index.ts
with the following code.
import { ponder } from "@/generated";
ponder.get("/hello", (c) => {
return c.text("Hello, world!");
});
Send a request
Visit http://localhost:42069/hello
in your browser to see the response.
Hello, world!
Register GraphQL middleware
Once you create a file in the src/api/
directory, Ponder stops serving the
standard GraphQL API.
To continue using the standard GraphQL API, register the graphql
middleware exported from @ponder/core
.
import { ponder } from "@/generated";
import { graphql } from "@ponder/core";
ponder.use("/", graphql());
ponder.use("/graphql", graphql());
// ...
Query the database
API functions can query the database using the read-only Select API, a type-safe query builder powered by Drizzle. The Select API supports complex filters, joins, aggregations, set operations, and more.
The Select API is only available within API functions. Indexing functions use
the Store API (findUnique
, upsert
,
etc) which supports writes and is reorg-aware.
Select
Use c.db
and c.tables
to build custom queries using Drizzle's Select query builder syntax.
import { ponder } from "@/generated";
ponder.get("/account/:address", async (c) => {
const { Account, TransferEvent } = c.tables;
const address = c.req.param("address");
const account = await c.db.select(account).where({ id: address }).first();
if (account) {
return c.json(account);
} else {
return c.status(404).json({ error: "Account not found" });
}
});
Execute
Run raw SQL queries with db.execute(...)
and the sql
helper function.
import { ponder } from "@/generated";
import { sql } from "@ponder/core";
ponder.get("/:token/ticker", async (c) => {
const token = c.req.param("token");
const result = await c.db.execute(
sql`SELECT ticker FROM "Token" WHERE id = ${token}`
);
const ticker = result.rows[0]?.ticker;
return c.text(ticker);
});
API reference
ponder.get()
Use ponder.get()
to handle HTTP GET
requests. The context (c
) contains the request object, response helpers, and the database connection.
import { ponder } from "@/generated";
ponder.get("/account/:address", async (c) => {
const { Account } = c.tables;
const address = c.req.param("address");
const account = await c.db
.select()
.from(Account)
.where({ id: address })
.first();
if (account) {
return c.json(account);
} else {
return c.status(404).json({ error: "Account not found" });
}
});
ponder.post()
Use ponder.post()
to handle HTTP POST
requests.
API functions cannot write to the database, even when handling POST
requests.
In this example, we calculate the volume of transfers for each recipient within a given time range. The fromTimestamp
and toTimestamp
parameters are passed in the request body.
import { ponder } from "@/generated";
import { gte } from "@ponder/core";
ponder.post("/volume", async (c) => {
const { TransferEvent } = c.tables;
const body = await c.req.json();
const { fromTimestamp, toTimestamp } = body;
const volumeChartData = await c.db
.select({
to: TransferEvent.toId,
volume: sum(TransferEvent.amount),
})
.from(TransferEvent)
.groupBy(TransferEvent.toId)
.where(
and(
gte(TransferEvent.timestamp, fromTimestamp),
lte(TransferEvent.timestamp, toTimestamp)
)
)
.limit(1);
return c.json(volumeChartData);
});
ponder.use()
Use ponder.use(...)
to add middleware to your API functions. Middleware functions can modify the request and response objects, add logs, authenticate requests, and more. Read more about Hono middleware.
import { ponder } from "@/generated";
ponder.use((c) => {
console.log("Request received:", c.req.url);
return c.next();
});
ponder.hono
If you need to access the underlying Hono instance, use the hono
property.
import { ponder } from "@/generated";
ponder.hono.notFound((c) => {
return c.text("Custom 404 Message", 404);
});
// ...
Reserved routes
If you register API functions that conflict with these internal routes, the build will fail.