# GraphQL \[Query the autogenerated GraphQL API]

Ponder automatically generates a [GraphQL](https://graphql.org/learn/) API based on the tables defined in `ponder.schema.ts`.

The GraphQL API is the quickest and easiest way to query data from a Ponder app.

## Register `graphql` middleware

To enable the GraphQL API, register the `graphql` Hono middleware.

```ts [src/api/index.ts]
import { db } from "ponder:api";
import schema from "ponder:schema";
import { Hono } from "hono";
import { graphql } from "ponder"; // [!code focus]

const app = new Hono();

app.use("/graphql", graphql({ db, schema })); // [!code focus]

export default app;
```

The `graphql()` middleware includes a [GraphiQL](https://github.com/graphql/graphiql/tree/main/packages/graphiql#graphiql) interface in response to GET requests (you can visit it in your browser). GraphiQL is a useful tool to explore your schema and test queries.

:::info
The `graphql()` middleware function also writes the `generated/schema.graphql` file during the build step. The `ponder codegen` command does not write `schema.graphql`.
:::

## Schema generation

The GraphQL schema includes a **singular** and a **plural** query field for each table in `ponder.schema.ts`.

If your schema contains a `person` table, the GraphQL schema will include a `person` and a `persons` field on the root `Query` type. The singular query field returns a single record (or null) and the plural query field returns a page of records.

:::code-group
```ts [ponder.schema.ts]
import { onchainTable } from "ponder";

export const person = onchainTable("person", (t) => ({
  id: t.integer().primaryKey(),
  name: t.text().notNull(),
  age: t.integer(),
}));
```

```graphql [GraphQL schema]
type person {
  id: Int!
  name: String!
  age: Int
}

type personPage {
  items: [person!]!
  pageInfo: PageInfo!
  totalCount: Int!
}

type Query {
  person(id: Int!, timestamp: Int): person
  persons(
    where: personFilter, 
    orderBy: String,
    orderDirection: String,
    before: String,
    after: String,
    limit: Int,
    timestamp: Int,
  ): personPage!
}
```
:::

## Filtering

Use the `where` argument to filter for records that match certain criteria. The `where` argument type includes filter options for every column defined on a table. Here are the filter options available for each column type.

| Filter option              | Available for column types    | *Include records where \{column}...*     |
| :------------------------- | :---------------------------- | :---------------------------------------- |
| `{column}`                 | All                           | **equals** the value                      |
| `{column}_not`             | All                           | **does not equal** the value              |
| `{column}_in`              | All primitives and enums      | **is one of** the values                  |
| `{column}_not_in`          | All primitives and enums      | **is not one of** the values              |
| `{column}_gt`              | Numeric primitives            | is **greater than** the value             |
| `{column}_lt`              | Numeric primitives            | is **less than** the value                |
| `{column}_gte`             | Numeric primitives            | is **greater than or equal to** the value |
| `{column}_lte`             | Numeric primitives            | is **less than or equal to** the value    |
| `{column}_contains`        | String primitives             | **contains** the substring                |
| `{column}_not_contains`    | String primitives             | **does not contain** the substring        |
| `{column}_starts_with`     | String primitives             | **starts with** the substring             |
| `{column}_not_starts_with` | String primitives             | **does not start with** the substring     |
| `{column}_ends_with`       | String primitives             | **ends with** the substring               |
| `{column}_not_ends_with`   | String primitives             | **does not end with** the substring       |
| `{column}_has`             | Lists of primitives and enums | **has** the value as an element           |
| `{column}_not_has`         | Lists of primitives and enums | **does not have** the value as an element |

You can compose filters using the `AND` and `OR` operators. These special fields accept an array of filter objects.

### Examples

For the following examples, assume these records exist in your database.

```json [person data]
[
  { "id": 1, "name": "Barry", "age": 57 },
  { "id": 2, "name": "Lucile", "age": 32 },
  { "id": 3, "name": "Sally", "age": 22 },
  { "id": 4, "name": "Pablo", "age": 71 },
]
```

Get all `person` records with an `age` greater than `32`.

:::code-group
```graphql [Query]
query {
  persons(where: { age_gt: 32 }) {
    name
    age
  }
}
```

```json [Result]
{
  "persons": [
    { "name": "Barry", "age": 57 },
    { "name": "Pablo", "age": 71 },
  ]
}
```
:::

Get all `person` records with a `name` that does not end with `"y"` *and* an age greater than `60`. Note that when you include multiple filter conditions, they are combined with a logical `AND`.

:::code-group
```graphql [Query]
query {
  persons(
    where: {
      AND: [
        { name_not_ends_with: "y" },
        { age_gte: 60 }
      ]
    }
  ) {
    name
    age
  }
}
```

```json [Result]
{
  "persons": [
    { "name": "Pablo", "age": 71 },
  ]
}
```
:::

Get all `person` records with a `name` that contains `"ll"` *or* an age greater than or equal to `50`. In this case, we use the special `OR` operator to combine multiple filter conditions.

:::code-group
```graphql [Query]
query {
  persons(
    where: {
      OR: [
        { name_contains: "ll" },
        { age_gte: 50 }
      ] 
    }
  ) {
    name
    age
  }
}
```

```json [Result]
{
  "persons": [
    { "name": "Barry", "age": 57 },
    { "name": "Sally", "age": 22 },
    { "name": "Pablo", "age": 71 },
  ]
}
```
:::

## Sorting

Use the `orderBy` and `orderDirection` arguments to sort the result by a column. By default, the result is sorted by the primary key column(s) in ascending order.

| Pagination option | Default               |
| :---------------- | :-------------------- |
| `orderBy`         | Primary key column(s) |
| `orderDirection`  | `"asc"`               |

### Examples

:::code-group
```graphql [Query]
query {
  persons(orderBy: "age", orderDirection: "desc") {
    name
    age
  }
}
```

```json [Result]
{
  "persons": [
    { "name": "Pablo", "age": 71 },
    { "name": "Barry", "age": 57 },
    { "name": "Lucile", "age": 32 },
    { "name": "Sally", "age": 22 },
  ]
}
```
:::

## Pagination

The GraphQL API supports cursor pagination with an API that's inspired by the [Relay GraphQL Cursor Connection](https://relay.dev/graphql/connections.htm#sec-undefined.PageInfo) specification.

Plural fields and `p.many()` relationship fields each return a `Page` type. This object contains a list of items, a `PageInfo` object, and the total count of records that match the query.

:::code-group
```ts [ponder.schema.ts]
import { onchainTable } from "ponder";

export const pet = onchainTable("pet", (t) => ({
  id: t.text().primaryKey(),
  name: t.text().notNull(),
}));
```

```graphql [Generated schema] {1-5}
type petPage { # [!code focus]
  items: [pet!]! # [!code focus]
  pageInfo: PageInfo! # [!code focus]
  totalCount: Int! # [!code focus]
} # [!code focus]

type pet {
  id: String!
  name: String!
}

type PageInfo {
  startCursor: String
  endCursor: String
  hasPreviousPage: Boolean!
  hasNextPage: Boolean!
}
```
:::

### Page info

The `PageInfo` object contains information about the position of the current page within the result set.

| name                | type                 |                                                 |
| :------------------ | :------------------- | :---------------------------------------------- |
| **startCursor**     | `String`             | Cursor of the first record in `items`           |
| **endCursor**       | `String`             | Cursor of the last record in `items`            |
| **hasPreviousPage** | `Boolean!`           | Whether there are more records before this page |
| **hasNextPage**     | `Boolean!`           | Whether there are more records after this page  |

### Total count

The `totalCount` field returns the number of records present in the database that match the specified query. The value is the same regardless of the current pagination position and the `limit` argument. Only the `where` argument can change the value of `totalCount`.

:::warning
The SQL query that backs `totalCount` can be slow. To avoid performance issues, consider including `totalCount` for the first page, then exclude it for subsequent pages. Unless the underlying data has changed, the value will be the same regardless of the pagination position.
:::

### Cursor values

Each cursor value is an opaque string that encodes the position of a record in the result set.

* Cursor values should not be decoded or manipulated by the client. The only valid use of a cursor value is an argument, e.g. `after: previousPage.endCursor`.
* Cursor pagination works with any filter and sort criteria. However, do not change the filter or sort criteria between paginated requests. This will cause validation errors or incorrect results.

### Examples

As a reminder, assume that these records exist in your database for the following examples.

```json [person data]
[
  { "id": 1, "name": "Barry", "age": 57 },
  { "id": 2, "name": "Lucile", "age": 32 },
  { "id": 3, "name": "Sally", "age": 22 },
  { "id": 4, "name": "Pablo", "age": 71 },
]
```

First, make a request without specifying any pagination options. The `items` list will contain the first `n=limit` records that match the filter and sort criteria.

:::code-group
```graphql [Query 1]
query {
  persons(orderBy: "age", orderDirection: "asc", limit: 2) {
    items {
      name
      age
    }
    pageInfo {
      startCursor
      endCursor
      hasPreviousPage
      hasNextPage
    }
    totalCount
  }
}
```

```json [Result 1]
{
  "persons" {
    "items": [
      { "name": "Sally", "age": 22 },
      { "name": "Lucile", "age": 32 },
    ],
    "pageInfo": {
      "startCursor": "MfgBzeDkjs44",
      "endCursor": "Mxhc3NDb3JlLTA=",
      "hasPreviousPage": false,
      "hasNextPage": true,
    },
    "totalCount": 4,
  }
}
```
:::

To paginate forwards, pass `pageInfo.endCursor` from the previous request as the `after` option in the next request.

:::code-group
```graphql [Query 2]
query {
  persons(
    orderBy: "age",
    orderDirection: "asc",
    limit: 2,
    after: "Mxhc3NDb3JlLTA=" # [!code focus]
  ) {
    items {
      name
      age
    }
    pageInfo {
      startCursor
      endCursor
      hasPreviousPage
      hasNextPage
    }
    totalCount
  }
}
```

```json [Result 2]
{
  "persons" {
    "items": [
      { "name": "Barry", "age": 57 },
      { "name": "Pablo", "age": 71 },
    ],
    "pageInfo": {
      "startCursor": "MxhcdoP9CVBhY",
      "endCursor": "McSDfVIiLka==",
      "hasPreviousPage": true,
      "hasNextPage": false,
    },
    "totalCount": 4,
  }
}
```
:::

To paginate backwards, pass `pageInfo.startCursor` from the previous request as the `before` option in the next request.

:::code-group
```graphql [Query 3]
query {
  persons(
    orderBy: "age",
    orderDirection: "asc",
    limit: 2,
    before: "MxhcdoP9CVBhY" # [!code focus]
  ) {
    items {
      name
      age
    }
    pageInfo {
      startCursor
      endCursor
      hasPreviousPage
      hasNextPage
    }
    totalCount
  }
}
```

```json [Result 3]
{
  "persons" {
    "items": [
      { "name": "Lucile", "age": 32 },
    ],
    "pageInfo": {
      "startCursor": "Mxhc3NDb3JlLTA=",
      "endCursor": "Mxhc3NDb3JlLTA=",
      "hasPreviousPage": true,
      "hasNextPage": true,
    },
    "totalCount": 4,
  }
}
```
:::

## Relationship fields

:::info
[Read more](/docs/0.10/schema/relations) about how to define relationships in your schema.
:::

The GraphQL schema includes a relationship field for each `one` or `many` relation defined in your schema. One-to-many fields are very similar to the top-level plural query field, except they are automatically filtered by the parent row ID.

:::code-group
```ts [ponder.schema.ts]
import { onchainTable, relations } from "ponder";

export const pet = onchainTable("pet", (t) => ({
  id: t.text().primaryKey(),
  name: t.text().notNull(),
  ownerId: t.integer().notNull(),
}));

export const petRelations = relations(pet, ({ one }) => ({
  owner: one(person, { fields: [pet.ownerId], references: [person.id] }),
}));

export const person = onchainTable("person", (t) => ({
  id: t.integer().primaryKey(),
}));

export const personRelations = relations(person, ({ many }) => ({
  dogs: many(pet),
}));
```

```graphql [Generated schema]
type pet {
  id: String!
  name: String!
  ownerId: Int!
  owner: person! # [!code focus]
}

type person {
  id: Int!
  pets( # [!code focus]
    # Has { ownerId: person.id } applied # [!code focus]
    where: petFilter, # [!code focus]
    orderBy: String, # [!code focus]
    orderDirection: String, # [!code focus]
    before: String, # [!code focus]
    after: String, # [!code focus]
    limit: Int, # [!code focus]
  ): petPage! # [!code focus]
}
```
:::

## Performance tips

Here are a few tips for speeding up slow GraphQL queries.

1. **Limit query depth**: Each layer of depth in a GraphQL query introduces at least one additional sequential database query. Avoid queries that are more than 2 layers deep.
2. **Use pagination**: Use cursor-based pagination to fetch records in smaller, more manageable chunks. This can help reduce the load on the database.
3. **Consider database indexes**: Consider creating database indexes to speed up filters, joins, and sort conditions. Keep in mind that relations **do not** automatically create indexes. [Read more](/docs/0.10/schema/tables#indexes).
4. **Enable horizontal scaling**: If the GraphQL API is struggling to keep up with request volume, consider spreading the load across multiple instances. [Read more](/docs/0.10/production/self-hosting#scale-the-http-server).
