Skip to main content

Pagination

Hysteria ORM provides two pagination strategies for efficient data retrieval: offset-based pagination (paginate) and cursor-based pagination (paginateWithCursor).

Offset-Based Pagination

paginate(page, perPage)

Returns a page of results along with metadata about the total result set.

const result = await sql.from(User).paginate(1, 10);

console.log(result.data); // User[]
console.log(result.paginationMetadata);
// {
// total: 100,
// perPage: 10,
// currentPage: 1,
// firstPage: 1,
// isEmpty: false,
// lastPage: 10,
// hasMorePages: true,
// hasPages: true,
// }

You can combine it with any query builder method:

const result = await sql
.from(User)
.where("status", "active")
.orderBy("createdAt", "desc")
.select("id", "name", "email")
.paginate(2, 25);

Pagination Metadata

The paginationMetadata object contains:

PropertyTypeDescription
totalnumberTotal number of matching records
perPagenumberNumber of records per page
currentPagenumberCurrent page number
firstPagenumberAlways 1
isEmptybooleantrue if total is 0
lastPagenumberLast page number (minimum 1)
hasMorePagesbooleantrue if there are pages after the current
hasPagesbooleantrue if total exceeds perPage

Raw Query Builder

The raw query builder also supports pagination:

const result = await sql.from("users").paginate(1, 10);
console.log(result.data); // Record<string, any>[]
console.log(result.paginationMetadata);

Cursor-Based Pagination

paginateWithCursor(limit, options, cursor?)

Cursor-based pagination is ideal for infinite-scroll UIs or real-time feeds where items may be inserted between page loads. Instead of using an offset, it uses a discriminator column value to determine where the next page starts.

// First page
const [firstPage, cursor] = await sql.from(User).paginateWithCursor(10, {
discriminator: "id",
orderBy: "asc",
});

console.log(firstPage.data); // User[]
console.log(cursor); // { key: "id", value: 42 }

// Next page — pass the cursor from the previous response
const [nextPage, nextCursor] = await sql.from(User).paginateWithCursor(
10,
{
discriminator: "id",
orderBy: "asc",
},
cursor,
);

Cursor Options

OptionTypeDescription
discriminatorkeyof ModelColumn to paginate on (usually the primary key)
orderBy"asc" | "desc"Sort direction (default: "asc")
operator">" | "<" etc.Comparison operator for cursor (default: ">")

Cursor Pagination Metadata

The cursor pagination metadata is simpler:

PropertyTypeDescription
totalnumberTotal number of matching records
perPagenumberNumber of records per page
firstPagenumberAlways 1
isEmptybooleantrue if total is 0

Chunking Large Datasets

The chunk method processes large datasets in manageable pieces without loading everything into memory:

for await (const users of sql.from(User).chunk(250)) {
await processUserBatch(users);
}

Choosing a Strategy

StrategyBest ForProsCons
paginateNumbered pages, admin dashboardsSimple API, total count, page metadataSlower on very deep offsets¹
paginateWithCursorInfinite scroll, real-time feedsConsistent performance at any depthNo random page access, no total pages
chunkBatch processing, data exportsMemory efficientNot for API responses

Performance Wrapper

Both pagination methods are available through the performance accessor, which wraps the call with execution timing:

const { data, time } = await sql.from(User).performance.paginate(1, 10);
console.log(`Query took ${time}ms`);
console.log(data.paginationMetadata);