Skip to main content

Overview

The TrackGeek API supports two pagination strategies to efficiently handle large datasets:
  1. Cursor-based pagination - Recommended for real-time feeds and large datasets
  2. Offset-based pagination - Traditional page-based navigation
Both methods limit the maximum items per page to 50 to ensure optimal performance.

Cursor Pagination

Cursor-based pagination uses an opaque cursor string to fetch the next set of results. This approach is more efficient for large datasets and handles real-time updates better than offset pagination.

Request Parameters

ParameterTypeRequiredDefaultConstraintsDescription
cursorstringNo--Opaque cursor string from previous response
itemsPerPagenumberNo-Min: 1, Max: 50Number of items to return
The first request should not include a cursor. Use the nextCursor from the response to fetch subsequent pages.

Response Format

interface CursorPaginationResult<T> {
  nextCursor: string | null;
  hasNextPage: boolean;
  items: T[];
}
  • nextCursor: Cursor for the next page, or null if no more results
  • hasNextPage: Boolean indicating if more results are available
  • items: Array of result items

Example Usage

interface Game {
  id: string;
  title: string;
  releaseDate: string;
}

interface CursorResponse {
  nextCursor: string | null;
  hasNextPage: boolean;
  items: Game[];
}

async function fetchAllGames() {
  const allGames: Game[] = [];
  let cursor: string | undefined;
  
  do {
    const params = new URLSearchParams({
      itemsPerPage: '20',
      ...(cursor && { cursor })
    });
    
    const response = await fetch(`https://api.trackgeek.com/games?${params}`);
    const data: CursorResponse = await response.json();
    
    allGames.push(...data.items);
    cursor = data.nextCursor ?? undefined;
    
  } while (cursor);
  
  return allGames;
}

Example Response

{
  "nextCursor": "eyJpZCI6IjEyMzQ1Njc4OTAiLCJ0aW1lc3RhbXAiOjE2MzI0MjUwMDB9",
  "hasNextPage": true,
  "items": [
    {
      "id": "game-1",
      "title": "The Legend of Zelda: Breath of the Wild",
      "releaseDate": "2017-03-03"
    },
    {
      "id": "game-2",
      "title": "Super Mario Odyssey",
      "releaseDate": "2017-10-27"
    }
  ]
}
Cursors are opaque strings and should not be parsed or modified. Always use the exact cursor string returned by the API.

Offset Pagination

Offset-based pagination uses page numbers to navigate through results. This is familiar and intuitive but can be less efficient for very large datasets.

Request Parameters

ParameterTypeRequiredDefaultConstraintsDescription
pagenumberNo-Min: 0Zero-based page number
itemsPerPagenumberNo-Min: 1, Max: 50Number of items per page

Response Format

interface OffsetPaginationResult<T> {
  count: number;
  pages: number;
  inPage: number;
  itemsInPage: number;
  itemsPerPage: number;
  items: T[];
}
  • count: Total number of items across all pages
  • pages: Total number of pages
  • inPage: Current page number (zero-based)
  • itemsInPage: Number of items in the current page
  • itemsPerPage: Requested items per page
  • items: Array of result items

Example Usage

interface Review {
  id: string;
  rating: number;
  content: string;
}

interface OffsetResponse {
  count: number;
  pages: number;
  inPage: number;
  itemsInPage: number;
  itemsPerPage: number;
  items: Review[];
}

async function fetchReviewsPage(page: number = 0, itemsPerPage: number = 10) {
  const params = new URLSearchParams({
    page: page.toString(),
    itemsPerPage: itemsPerPage.toString()
  });
  
  const response = await fetch(`https://api.trackgeek.com/reviews?${params}`);
  const data: OffsetResponse = await response.json();
  
  console.log(`Page ${data.inPage + 1} of ${data.pages}`);
  console.log(`Showing ${data.itemsInPage} of ${data.count} total items`);
  
  return data;
}

// Fetch all pages
async function fetchAllReviews() {
  const firstPage = await fetchReviewsPage(0, 50);
  const allReviews = [...firstPage.items];
  
  for (let page = 1; page < firstPage.pages; page++) {
    const pageData = await fetchReviewsPage(page, 50);
    allReviews.push(...pageData.items);
  }
  
  return allReviews;
}

Example Response

{
  "count": 156,
  "pages": 16,
  "inPage": 0,
  "itemsInPage": 10,
  "itemsPerPage": 10,
  "items": [
    {
      "id": "review-1",
      "rating": 5,
      "content": "Amazing game!"
    },
    {
      "id": "review-2",
      "rating": 4,
      "content": "Really enjoyed it."
    }
  ]
}
Page numbers are zero-based. The first page is page=0, not page=1.

Choosing the Right Pagination Method

Use Cursor Pagination When:

  • Working with real-time data that updates frequently
  • Fetching large datasets (thousands of records)
  • Building infinite scroll interfaces
  • You need consistent results even as data changes

Use Offset Pagination When:

  • Building traditional page-based UI (page 1, 2, 3…)
  • You need to know the total count upfront
  • Users need to jump to specific pages
  • Working with relatively small, stable datasets

Validation

Both pagination methods include validation to ensure optimal performance:
class CursorPaginationParamsDto {
  @IsOptional()
  @IsString()
  readonly cursor?: string;
  
  @Type(() => Number)
  @IsInt()
  @IsOptional()
  @Min(1)
  @Max(50)
  readonly itemsPerPage?: number;
}

class OffsetPaginationParamsDto {
  @Type(() => Number)
  @IsInt()
  @IsOptional()
  readonly page?: number;
  
  @Type(() => Number)
  @IsInt()
  @IsOptional()
  @Min(1)
  @Max(50)
  readonly itemsPerPage?: number;
}
Requesting more than 50 items per page will result in a validation error. Break large requests into multiple paginated calls.

Performance Tips

  1. Set appropriate page sizes: Balance between fewer requests (larger pages) and faster response times (smaller pages)
  2. Cache results: Store paginated results on the client to avoid redundant requests
  3. Use cursor pagination for feeds: Activity feeds and timelines benefit from cursor-based pagination
  4. Respect rate limits: Be mindful of rate limits when fetching multiple pages

Implementation Reference

The pagination DTOs are defined at:
  • Cursor pagination: src/shared/infra/database/dtos/cursor-pagination.dto.ts:1-31
  • Offset pagination: src/shared/infra/database/dtos/offset-pagination.dto.ts:1-36

Build docs developers (and LLMs) love