Skip to content

paginateQuery — custom count & data pagination ​

Imported from the facade: import { paginateQuery } from "@/framework/facade.js".

Pagination with manual total() / data() callbacks — for when the count query and the data query are structurally different (JOIN + GROUP BY + HAVING, DISTINCT, aggregates). See Database.

Signature ​

FunctionSignatureDescription
paginateQuery(options) => Promise<PaginatedResult<T>>Manual total() / data() callbacks

Options:

OptionTypeDescription
pagenumberPage number
perPagenumberItems per page
totalasync () => numberRequired. Runs the count query
dataasync (limit, offset) => T[]Required. Runs the data query with limit/offset
pathstringBase path for pagination URLs

Use cases ​

Aggregates with HAVING ​

ts
import { count, eq, gt, sum } from "drizzle-orm";
import { db, paginateQuery } from "@/framework/facade.js";

const result = await paginateQuery({
  page: 1,
  perPage: 15,
  total: async () => {
    const [row] = await db
      .select({ total: count() })
      .from(users)
      .where(eq(users.active, true));
    return Number(row?.total ?? 0);
  },
  data: async (limit, offset) =>
    db
      .select({ id: users.id, name: users.name, orderCount: count(orders.id) })
      .from(users)
      .leftJoin(orders, eq(users.id, orders.userId))
      .groupBy(users.id)
      .having(gt(count(orders.id), 5))
      .limit(limit)
      .offset(offset),
});

Notes ​

  • Use only when paginate/paginateModel/paginateTable can't express the count query (e.g. HAVING-filtered groups, window functions).
  • Returns the same PaginatedResult shape as the other three helpers.

Released under the MIT License.