Skip to content

Instantly share code, notes, and snippets.

@CodeLikeAGirl29
Created July 20, 2026 09:46
Show Gist options
  • Select an option

  • Save CodeLikeAGirl29/d8931c604f4bb643f05757d6fecfeba6 to your computer and use it in GitHub Desktop.

Select an option

Save CodeLikeAGirl29/d8931c604f4bb643f05757d6fecfeba6 to your computer and use it in GitHub Desktop.
Generic cursor-based pagination helper for Prisma. Works with any model — pass the delegate plus your usual where/orderBy/select. Cursor pagination stays stable when rows are added or deleted between page loads, unlike offset/skip. Includes a usage example for fetching sequential pages.
/**
* Generic cursor-based pagination helper for Prisma.
*
* Cursor pagination is stable under inserts/deletes (unlike offset/skip,
* which can skip or repeat rows when the underlying data changes between
* page loads) and scales far better on large tables.
*
* Works with any Prisma model — pass the delegate (e.g. prisma.user,
* prisma.complianceLog) and your usual where/orderBy/select options.
*
* npm install @prisma/client
*/
type PrismaDelegate<T> = {
findMany: (args: any) => Promise<T[]>
}
interface PaginateArgs<T> {
model: PrismaDelegate<T>
take?: number
cursor?: string | number | null
cursorField?: string
where?: Record<string, any>
orderBy?: Record<string, 'asc' | 'desc'>
select?: Record<string, any>
include?: Record<string, any>
}
interface PaginatedResult<T> {
items: T[]
nextCursor: string | number | null
hasMore: boolean
}
export async function paginate<T extends Record<string, any>>({
model,
take = 20,
cursor = null,
cursorField = 'id',
where = {},
orderBy = { [cursorField]: 'asc' },
select,
include,
}: PaginateArgs<T>): Promise<PaginatedResult<T>> {
const queryArgs: Record<string, any> = {
where,
orderBy,
// Fetch one extra row so we can tell if there's a next page
// without a separate count query.
take: take + 1,
}
if (select) queryArgs.select = select
if (include) queryArgs.include = include
if (cursor !== null) {
queryArgs.cursor = { [cursorField]: cursor }
queryArgs.skip = 1 // skip the cursor row itself
}
const results = await model.findMany(queryArgs)
const hasMore = results.length > take
const items = hasMore ? results.slice(0, take) : results
const nextCursor = hasMore ? items[items.length - 1][cursorField] : null
return { items, nextCursor, hasMore }
}
// -----------------------------------------------------------------------
// Example usage:
//
// import { prisma } from '@/lib/prisma'
// import { paginate } from '@/lib/paginate'
//
// const page1 = await paginate({
// model: prisma.complianceLog,
// take: 25,
// where: { propertyId: '123' },
// orderBy: { createdAt: 'desc' },
// cursorField: 'id',
// })
//
// // Fetch the next page:
// const page2 = await paginate({
// model: prisma.complianceLog,
// take: 25,
// cursor: page1.nextCursor,
// where: { propertyId: '123' },
// orderBy: { createdAt: 'desc' },
// cursorField: 'id',
// })
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment