Documentation
Query the GraphQL API

Query the GraphQL API

This guide assumes you have a working knowledge of GraphQL query syntax. For a general overview of GraphQL, please refer to the documentation (opens in a new tab).

Ponder uses your ponder.schema.ts file to generate a GraphQL API for your app. With the dev server running, open http://localhost:42069 in your browser to use the GraphiQL (opens in a new tab) interface. GraphiQL is a useful tool for exploring your schema and testing queries during development.

Schema generation

Ponder creates a singular and a plural query field for each table in your schema. For example, if your schema contains a Person table, Ponder will create a person and a persons field on the root Query type. The singular query field returns a single record (or null), while the plural query field returns a list of records.

ponder.schema.ts
import { createSchema } from "@ponder/core";
 
export default createSchema((p) => ({
  Person: p.createTable({
    id: p.int(),
    name: p.string(),
    age: p.int().optional(),
  }),
}));
Generated schema
type Person {
  id: Int!
  name: String!
  age: Int
}
 
type Query {
  person(
    id: Int!,
    timestamp: Int
  ): Person
  persons(
    skip: Int = 0,
    first: Int = 100,
    orderBy: String = "id",
    orderDirection: String = "asc",
    where: PersonFilter,
    timestamp: Int
  ): [Person!]!
}

Filtering

The GraphQL API supports filtering through the where argument. The where argument type contains filter options for every column defined on your table. Here are the filter options available for each field type.

Filter optionAvailable for column typesInclude records where {column}...
{column}Allequals the value
{column}_notAlldoes not equal the value
{column}_inAll primitives and enumsis one of the values
{column}_not_inAll primitives and enumsis not one of the values
{column}_gtNumeric primitives (int, float, bigint)is greater than the value
{column}_ltNumeric primitivesis less than the value
{column}_gteNumeric primitivesis greater than or equal to the value
{column}_lteNumeric primitivesis less than or equal to the value
{column}_containsString primitives (string, bytes)contains the substring
{column}_not_containsString primitivesdoes not contain the substring
{column}_starts_withString primitivesstarts with the substring
{column}_not_starts_withString primitivesdoes not start with the substring
{column}_ends_withString primitivesends with the substring
{column}_not_ends_withString primitivesdoes not end with the substring
{column}_hasLists of primitives and enumshas the value as an element
{column}_not_hasLists of primitives and enumsdoes not have the value as an element

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

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:

Query
query {
  persons(where: { age_gt: 32 }) {
    name
    age
  }
}
Result
{
  "persons": [
    { "name": "Barry", "age": 57 },
    { "name": "Pablo", "age": 71 },
  ]
}

Get all Person records with a name that does not end with "y":

Query
query {
  persons(where: { name_not_ends_with: "y" }) {
    name
    age
  }
}
Result
{
  "persons": [
    { "name": "Lucile", "age": 32 },
    { "name": "Pablo", "age": 71 },
  ]
}

Pagination

The GraphQL API supports pagination through the first and skip arguments.

Pagination optionDefaultMax
first1001000
skip05000
Query
query {
  persons(first: 2, skip: 1) {
    name
    age
  }
}
Result
{
  "persons": [
    { "name": "Lucile", "age": 32 },
    { "name": "Sally", "age": 22 },
  ]
}
⚠️

The default and max first and skip values are also applied to virtual fields. If you find youself needing to paginate through more than 1000 items in a virtual field, strongly consider writing a new query that fetches those items at the root level.

Sorting

Use the orderBy and orderDirection arguments to sort records by a column. String primitive (string, bytes) values are sorted lexicographically.

Pagination optionDefault
orderBy"id"
orderDirection"asc"
Query
query {
  persons(orderBy: "age", orderDirection: "desc") {
    name
    age
  }
}
Result
{
  "persons": [
    { "name": "Pablo", "age": 71 },
    { "name": "Barry", "age": 57 },
    { "name": "Lucile", "age": 32 },
    { "name": "Sally", "age": 22 },
  ]
}

Time-travel queries

Using time-travel queries, you can query the state of your app's database at any point in history. To construct a time-travel query, pass a Unix timestamp to the timestamp argument on any of the root query types.

Time-travel optionDefault
timestampundefined ("latest")

In this example, consider that only Pablo had been added to the database at the specified timestamp, and his age at that time was 42. The other records were inserted later.

Query
query {
  persons(timestamp: 1689910567) {
    name
    age
  }
}
Result
{
  "persons": [
    { "name": "Pablo", "age": 42 },
  ]
}

Relationship fields

See the Define your schema guide for a detailed overview of how to define relationships in your schema.

When you define a column in your schema using p.one() and p.many(), Ponder automatically creates a .

Fields created by p.many() are very similar to the top-level plural query field, except they are automatically filtered by the parent entity ID.

ponder.schema.ts
import { createSchema } from "@ponder/core";
 
export default createSchema((p) => ({
  Person: p.createTable({
    id: p.int(),
    dogs: p.many("Pet.ownerId"),
  }),
  Pet: p.createTable({
    id: p.string(),
    name: p.string(),
    ownerId: p.int().references("Person.id"),
  }),
}));
Generated schema
type Person {
  id: Int!
  pets(
    skip: Int = 0,
    first: Int = 100,
    orderBy: String = "id",
    orderDirection: String = "asc",
    # This automatically has { ownerId: person.id } applied
    where: PetFilter, 
    timestamp: Int
  ): [Pet!]!
}