Unpaginated list endpoints are a denial-of-service waiting to happen: as data grows, findMany({}) with no take fetches every record in the table into memory, exhausting database connection pool, server RAM, and response bandwidth simultaneously. This maps directly to CWE-770 (allocation of resources without limits) and OWASP API 2023 API4 (unrestricted resource consumption). A single authenticated request can trigger minutes of database load. Cost-per-request grows linearly with data, making unbounded lists a latent cost bomb in addition to a reliability risk.
High because even a single unbounded list query can exhaust database memory and connection pool, causing a denial-of-service condition that affects all users.
Add offset-based or cursor-based pagination to every list endpoint in app/api/. Accept page and limit query params, enforce a maximum page size, and include pagination metadata in the response:
const page = Number(searchParams.get('page') ?? 1)
const limit = Math.min(Number(searchParams.get('limit') ?? 20), 100)
const skip = (page - 1) * limit
const [items, total] = await Promise.all([
db.post.findMany({ skip, take: limit }),
db.post.count(),
])
return apiSuccess({ items, meta: { page, limit, total } })
For high-volume tables, replace offset pagination with cursor-based pagination (pass the last record's id as a cursor) to avoid offset drift and index scans.
saas-api-design.request-response.pagination-list-endpointshighfindMany, select *, array returns, .getAll(), .list(), cursor-based queries). Check whether each list endpoint implements pagination. Acceptable pagination patterns: offset/limit (?page=1&limit=20), cursor-based (?cursor=<id>&limit=20), keyset pagination. Also check: is there a maximum limit enforced to prevent ?limit=999999?findMany({}) or equivalent with no take/limit/skip applied.findMany, selectAll, .list(), or array-returning queries found in any route handler.app/api/ route handlers. The simplest approach is offset-based pagination: accept page and limit query parameters, apply them to the database query, and include pagination metadata in the response. For example in Prisma: findMany({ skip: (page-1)*limit, take: Math.min(limit, 100) }). Return { data: items, meta: { page, limit, total } } so clients can implement pagination UI. For high-volume datasets, consider cursor-based pagination instead — it's more efficient and avoids offset drift. Always enforce a maximum limit to prevent clients from requesting unlimited data.