-
Notifications
You must be signed in to change notification settings - Fork 111
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Custom API Functions #943
Custom API Functions #943
Changes from 110 commits
11c7f36
8358ebd
96de148
dd74238
41a81cf
8827016
5a1ae49
2b23657
da92dcb
419a461
526d717
8af92a2
3bc6803
d55a2a5
63d3a78
56fd057
bbb1eab
821fb29
f67fdb5
ef36400
b5c8e49
5f36800
f221186
f0468f8
0134f38
ea5e16d
e3ed3b8
b88e6dd
350ff28
d9bb141
210adb7
1bb9f57
ee9d6aa
96f4e04
e33c7e9
c349d41
5c4bf59
aaa044d
cb12dc6
0fae4bf
1a7762f
761bfe5
6254e26
b159182
47e3ada
5905e57
4b7652f
979cb9b
3e60f95
f018a69
a171ab4
4e05dfc
791a1eb
9484a13
87f5137
a9a00e3
8685c9e
f6e6713
c376e90
f0f818d
acf2309
a601122
f38aadf
0fe58bd
03e36b5
020f70e
ebb4076
9afab48
2d0e3c5
9809d14
9346d82
542e7aa
0ee458a
95f18e4
aaf9d2f
bdd94cd
fb97142
ea2e215
40ec552
ea520ac
b9b5f65
3897be3
5ef606d
a554e3f
0c13213
047d76b
ed57d99
518d608
08235b4
95f9313
4fdf813
5932a69
9c73c24
8dbb489
1037403
321728e
b188f6f
40e8a9b
d3ac5e8
76d9e4c
b9d8955
9181ad1
81ce418
377af20
ce0077f
4a2681e
d99266d
40b2ba0
12fec05
5e82d47
411b638
8672adf
7ea2e35
bb270da
6e029c5
17a27d6
b5b3869
646cd1e
1cc22a1
d54af6d
71a56cd
757f601
ecf4d7c
c03df64
dfe2a78
411434a
c8c49e6
7b3466b
4822f8e
4012209
b7e5016
63a3a23
5cab628
1c539bc
b419b84
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
--- | ||
"create-ponder": minor | ||
"@ponder/core": minor | ||
--- | ||
|
||
[Experimental] Introduced API functions. [Read more](https://ponder-docs-git-kjs-api2-ponder-sh.vercel.app/docs/query/api-functions). |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
"@ponder/core": patch | ||
--- | ||
|
||
Added per network progress to the "/status" endpoint on the server and the "_metadata" entity on the GraphQL schema. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
export default { | ||
"api-functions": "API functions (experimental)", | ||
"graphql": "GraphQL", | ||
"direct-sql": "Direct SQL", | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
--- | ||
title: "API functions" | ||
description: "Use API functions to customize the API layer of your app." | ||
--- | ||
|
||
import { Callout, Steps } from "nextra/components"; | ||
|
||
# API functions (experimental) | ||
|
||
**API functions** are user-defined functions that respond to incoming Web requests. You can use them to customize the API layer of your app with direct SQL queries, authentication, data from external sources, and more. | ||
|
||
API functions are built on top of [Hono](https://hono.dev/), a fast and lightweight routing framework. | ||
|
||
## Get started | ||
|
||
<Steps> | ||
|
||
### Install `hono` and `@ponder/core@next` | ||
|
||
To use API functions, install `hono` and the prerelease version of `@ponder/core`. | ||
|
||
```bash | ||
npm install hono @ponder/core@next | ||
``` | ||
|
||
### Create `src/api/index.ts` file | ||
|
||
To opt-in to API functions, create a file named `src/api/index.ts` with the following code. | ||
|
||
```ts filename="src/api/index.ts" | ||
import { ponder } from "@/generated"; | ||
|
||
ponder.get("/hello", (c) => { | ||
return c.text("Hello, world!"); | ||
}); | ||
``` | ||
|
||
### Send a request | ||
|
||
With the server running, visit `http://localhost:42069/hello` in your browser to see the response. | ||
|
||
### Add the GraphQL middleware | ||
|
||
To register the standard GraphQL API that was automatically included in versions `<0.5.0`, register the `graphql` middleware exported from `@ponder/core`. | ||
|
||
```ts filename="src/api/index.ts" {2,4} | ||
import { ponder } from "@/generated"; | ||
import { graphql } from "@ponder/core"; | ||
|
||
ponder.use("/graphql", graphql()); | ||
|
||
// ... | ||
``` | ||
|
||
</Steps> | ||
|
||
## Query the database | ||
|
||
API functions have read-only access to your database using a [Hono Variable](https://hono.dev/docs/api/context#set-get) named `db`. Unlike indexing functions, the `create`, `update`, `delete`, `createMany`, and `updateMany` methods are not available in API functions. | ||
|
||
### `findUnique` and `findMany` | ||
|
||
API functions can use `findUnique` and `findMany` to query the database. These methods work just like they do within indexing functions. [Read more](/docs/indexing/create-update-records#findmany) about the store API. | ||
|
||
```ts filename="src/api/index.ts" {5,7-9} | ||
import { ponder } from "@/generated"; | ||
|
||
ponder.get("/account/:address", (c) => { | ||
const address = c.req.param("address"); | ||
const db = c.get("db"); | ||
|
||
const account = await db.Account.findUnique({ | ||
id: address, | ||
}); | ||
|
||
if (account) { | ||
return c.json(account); | ||
} else { | ||
return c.status(404).json({ error: "Account not found" }); | ||
} | ||
}); | ||
``` | ||
|
||
### Direct SQL | ||
|
||
Use `db.query(...){:ts}` to execute raw SQL queries against your database. | ||
|
||
```ts filename="src/api/index.ts" {6,8-10} | ||
import { ponder } from "@/generated"; | ||
import { sql } from "@ponder/core"; | ||
|
||
ponder.get("/:token/ticker", (c) => { | ||
const token = c.req.param("token"); | ||
const db = c.get("db"); | ||
|
||
const result = await db.query( | ||
sql`SELECT ticker FROM "Token" WHERE id = ${token}` | ||
); | ||
const ticker = result.rows[0]?.ticker; | ||
|
||
return c.text(ticker); | ||
}); | ||
``` | ||
|
||
## Register middleware | ||
|
||
Use `ponder.use(...){:ts}` to add middleware to your API functions. Middleware functions can modify the request and response objects, add logs, do authentication, and more. [Read more](https://hono.dev/docs/guides/middleware) about Hono middleware. | ||
|
||
```ts filename="src/api/index.ts" {3} | ||
import { ponder } from "@/generated"; | ||
|
||
ponder.use((c) => { | ||
console.log("Request received:", c.req.url); | ||
return c.next(); | ||
}); | ||
``` | ||
|
||
## Access the Hono instance | ||
|
||
You can register API functions and middleware using `ponder.get(...){:ts}`, `ponder.post(...){:ts}`, and `ponder.use(...){:ts}`. If you need to access the underlying Hono instance, use the `hono` property. | ||
|
||
```ts filename="src/api/index.ts" {3} | ||
import { ponder } from "@/generated"; | ||
|
||
ponder.hono.notFound((c) => { | ||
return c.text("Custom 404 Message", 404); | ||
}); | ||
|
||
// ... | ||
``` | ||
|
||
## Internal routes | ||
|
||
Ponder registers internal routes under the `/_ponder/*` path. If you attempt to register API functions that conflict with internal routes, the build will fail. | ||
|
||
- `/_ponder/health`: Returns a `200` status code after the app has completed historical indexing OR the healthcheck timeout has expired, whichever comes first. [Read more](/docs/production/zero-downtime) about healthchecks. | ||
- `/_ponder/metrics`: Returns Prometheus metrics. [Read more](/docs/advanced/metrics) about metrics. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { ponder } from "@/generated"; | ||
import { desc, graphql } from "@ponder/core"; | ||
import { formatEther } from "viem"; | ||
|
||
ponder.use("/graphql", graphql()).get("/big", async (c) => { | ||
const { Account } = c.tables; | ||
|
||
const account = await c.db | ||
.select({ balance: Account.balance }) | ||
.from(Account) | ||
.orderBy(desc(Account.balance)) | ||
.limit(1); | ||
|
||
if (account.length === 0) { | ||
return c.text("Not Found!"); | ||
} else { | ||
return c.text(`Balance: ${formatEther(account[0]!.balance)}`); | ||
} | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -17,13 +17,17 @@ | |
"@biomejs/biome": "^1.8.1", | ||
"@changesets/changelog-github": "^0.4.8", | ||
"@changesets/cli": "^2.26.2", | ||
"hono": "4.0.0", | ||
"lint-staged": "^15.1.0", | ||
"simple-git-hooks": "^2.9.0", | ||
"typescript": "5.0.4", | ||
"viem": "1.16.0" | ||
}, | ||
"lint-staged": { | ||
"*.ts": ["biome format --no-errors-on-unmatched --write", "biome check"], | ||
"*.ts": [ | ||
"biome format --no-errors-on-unmatched --write", | ||
"biome check --no-errors-on-unmatched" | ||
], | ||
"!(*.ts)": ["biome format --no-errors-on-unmatched --write"] | ||
}, | ||
"simple-git-hooks": { | ||
|
@@ -32,7 +36,6 @@ | |
"packageManager": "pnpm@8.6.10", | ||
"pnpm": { | ||
"patchedDependencies": { | ||
"graphql@16.8.1": "patches/graphql@16.8.1.patch", | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can you remind me why we're able to remove this? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The patched dependency wasn't working with the new middleware setup, my guess is some vite thing because vite is executing that code. |
||
"detect-package-manager@3.0.1": "patches/detect-package-manager@3.0.1.patch" | ||
}, | ||
"peerDependencyRules": { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// @ts-ignore | ||
import { ponder } from "@/generated"; | ||
import { graphql } from "@/index.js"; | ||
|
||
// biome-ignore lint/suspicious/noRedeclare: :) | ||
declare const ponder: import("@/index.js").Virtual.Registry< | ||
typeof import("../../ponder.config.js").default, | ||
typeof import("../../ponder.schema.js").default | ||
>; | ||
|
||
ponder.use("/graphql", graphql()); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
// @ts-ignore | ||
import { ponder } from "@/generated"; | ||
import { graphql } from "@/index.js"; | ||
|
||
// biome-ignore lint/suspicious/noRedeclare: :) | ||
declare const ponder: import("@/index.js").Virtual.Registry< | ||
typeof import("../../ponder.config.js").default, | ||
typeof import("../../ponder.schema.js").default | ||
>; | ||
|
||
ponder.use("/graphql", graphql()); |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -694,10 +694,12 @@ export async function waitForIndexedBlock( | |
reject(new Error("Timed out while waiting for the indexed block.")); | ||
}, 5_000); | ||
const interval = setInterval(async () => { | ||
const response = await fetch(`http://localhost:${port}/status`); | ||
const response = await fetch(`http://localhost:${port}/_ponder/status`); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Reminder to remove the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nice catch |
||
if (response.status === 200) { | ||
const status = (await response.json()) as Status; | ||
const statusBlockNumber = status[networkName]?.block?.number; | ||
const status = (await response.json()) as Status | null; | ||
const statusBlockNumber = status | ||
? status[networkName]?.block?.number | ||
: undefined; | ||
if ( | ||
statusBlockNumber !== undefined && | ||
statusBlockNumber >= blockNumber | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -57,7 +57,7 @@ export async function codegen({ cliOptions }: { cliOptions: CliOptions }) { | |
properties: { cli_command: "codegen" }, | ||
}); | ||
|
||
runCodegen({ common, graphqlSchema: buildResult.build.graphqlSchema }); | ||
runCodegen({ common, graphQLSchema: buildResult.build.graphQLSchema }); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we move this to userland in this PR? Also, any reason for the rename here? IMO still think |
||
|
||
logger.info({ service: "codegen", msg: "Wrote ponder-env.d.ts" }); | ||
logger.info({ service: "codegen", msg: "Wrote schema.graphql" }); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is "Web" supposed to be capitalized