Pagination

Pagination

Queries that return lists, like cars, customers and visits, are paginated with cursors. Each returns a page type, such as CarPage, with two fields:

  • nodes: the records on this page.
  • pageInfo: where this page sits in the list, and the cursors to get the pages around it.

Pagination arguments go inside the query’s input object, alongside its filters:

query Cars($input: CarsQueryInput) {
  cars(input: $input) {
    nodes {
      id
      plate
    }
    pageInfo {
      hasNextPage
      endCursor
    }
  }
}
{ "input": { "first": 25 } }

Arguments

Argument Meaning
first Return up to this many records from the start of the range.
after Start after the record with this cursor.
last Return up to this many records from the end of the range.
before End before the record with this cursor.

Use first with after to page forward, and last with before to page backward. Don’t mix the two directions in one request.

  • With no first or last, a page has 20 records.
  • A page can have at most 100 records.

Larger pages cost more against your rate limit, because a page’s selection is multiplied by its page size.

Page forward

Request the first page, then pass each page’s pageInfo.endCursor as after until hasNextPage is false:

async function fetchAllCars(accessToken) {
  const cars = [];
  let after = null;

  do {
    const response = await fetch("https://api.rollerup.tech/graphql", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        Authorization: `Bearer ${accessToken}`,
      },
      body: JSON.stringify({
        query: `query Cars($input: CarsQueryInput) {
          cars(input: $input) {
            nodes { id plate }
            pageInfo { hasNextPage endCursor }
          }
        }`,
        variables: { input: { first: 100, after } },
      }),
    });
    const { data, errors } = await response.json();
    if (errors) throw new Error(errors[0].message);

    cars.push(...data.cars.nodes);
    after = data.cars.pageInfo.hasNextPage ? data.cars.pageInfo.endCursor : null;
  } while (after);

  return cars;
}

Keep the filters the same on every request. A cursor marks a position in one particular list, so it only works with the filters that produced it.

Page backward

To read from the end of the range, use last. For the page before, pass pageInfo.startCursor as before, and continue while hasPreviousPage is true. Records within each page keep the list’s normal order.

pageInfo

Field Meaning
hasNextPage Paging forward: whether there are more records after this page.
hasPreviousPage Paging backward: whether there are more records before this page.
startCursor Cursor of the first record on this page, or null if the page is empty.
endCursor Cursor of the last record on this page, or null if the page is empty.

Each flag is only checked in the direction you’re paging. Paging forward, hasPreviousPage just tells you whether you passed after. Paging backward, hasNextPage just tells you whether you passed before.

Cursors

Cursors are opaque strings. Treat them as tokens: pass back exactly what the API gave you, and don’t build or parse your own. Their format can change.

Each list has a fixed order, newest first unless the query’s description says otherwise, with ties broken by creation time and then ID. A cursor marks a position in that order, not a snapshot of the data:

  • Adding or deleting records while you page won’t make you skip a record or see one twice.
  • New records sort to the start of a newest-first list, so a forward walk that’s already underway won’t include them. Start again from the first page to pick them up.

Errors

These come back as GraphQL errors in the errors array:

Message Cause
Invalid cursor The cursor wasn’t one the API issued, or it was altered.
Cannot specify both 'first' and 'last' limits at the same time. Both first and last were set.
Cannot specify both 'after' and 'before' cursors at the same time. Both after and before were set.
Cannot use 'first' with 'before'. Use 'last' with 'before' for backward pagination. The two directions were mixed.
Cannot use 'last' with 'after'. Use 'first' with 'after' for forward pagination. The two directions were mixed.
Cannot request more than 100 items per page. first or last was over 100. This one has the VALIDATION code.