pagination

v2026.09.24

API pagination patterns. Offset-based, cursor-based, keyset pagination. Filtering, sorting, and page metadata. REST and GraphQL pagination implementations. USE WHEN: user mentions "pagination", "paginate", "cursor", "offset", "page size", "next page", "infinite scroll API", "list endpoint" DO NOT USE FOR: frontend infinite scroll UI - use frontend framework skills; database query optimization - use database skills

GitHub
安装命令
npx skhub add claude-dev-suite/pagination
Markdown
SKILL.md

Pagination

Cursor-Based (recommended for large datasets)

app.get('/api/products', async (req, res) => {
  const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
  const cursor = req.query.cursor as string | undefined;

  const where: any = {};
  if (cursor) {
    where.id = { gt: cursor };
  }

  const items = await db.product.findMany({
    where,
    take: limit + 1, // Fetch one extra to check hasMore
    orderBy: { id: 'asc' },
  });

  const hasMore = items.length > limit;
  if (hasMore) items.pop();

  res.json({
    data: items,
    pagination: {
      hasMore,
      nextCursor: hasMore ? items[items.length - 1].id : null,
    },
  });
});

Offset-Based (simple, good for small datasets)

app.get('/api/products', async (req, res) => {
  const page = Math.max(parseInt(req.query.page as string) || 1, 1);
  const limit = Math.min(parseInt(req.query.limit as string) || 20, 100);
  const offset = (page - 1) * limit;

  const [items, total] = await Promise.all([
    db.product.findMany({ skip: offset, take: limit, orderBy: { createdAt: 'desc' } }),
    db.product.count(),
  ]);

  res.json({
    data: items,
    pagination: {
      page, limit, total,
      totalPages: Math.ceil(total / limit),
      hasMore: offset + items.length < total,
    },
  });
});

Filtering and Sorting

app.get('/api/products', async (req, res) => {
  const { sort = 'createdAt', order = 'desc', category, minPrice, maxPrice, search } = req.query;

  const where: any = {};
  if (category) where.category = category;
  if (minPrice || maxPrice) {
    where.price = {};
    if (minPrice) where.price.gte = parseFloat(minPrice as string);
    if (maxPrice) where.price.lte = parseFloat(maxPrice as string);
  }
  if (search) where.name = { contains: search, mode: 'insensitive' };

  const items = await db.product.findMany({
    where,
    orderBy: { [sort as string]: order },
    take: limit,
    skip: offset,
  });

  res.json({ data: items, pagination: { /* ... */ } });
});

Spring Boot (Pageable)

@GetMapping("/products")
public Page<ProductDto> list(
    @RequestParam(defaultValue = "0") int page,
    @RequestParam(defaultValue = "20") int size,
    @RequestParam(defaultValue = "createdAt,desc") String[] sort) {

    Pageable pageable = PageRequest.of(page, Math.min(size, 100),
        Sort.by(Sort.Direction.fromString(sort[1]), sort[0]));
    return productRepo.findAll(pageable).map(mapper::toDto);
}

Comparison

StrategyProsConsBest For
OffsetSimple, jump to pageSlow on large tables, skip driftAdmin panels, small datasets
CursorFast, stable with insertsCan't jump to page NFeeds, infinite scroll, large datasets
KeysetFast, no skip driftComplex multi-column sortTime-series, ordered data

Anti-Patterns

Anti-PatternFix
No max page sizeCap limit (e.g., max 100)
COUNT(*) on huge tablesUse cursor pagination, skip total count
Offset on millions of rowsUse cursor or keyset pagination
Returning all fieldsSelect only needed fields, support fields param
No default sortingAlways define default sort for stable results

Production Checklist

  • Maximum page size enforced (e.g., 100)
  • Default sort order defined
  • Cursor pagination for large/growing datasets
  • Input validation on page/limit/sort params
  • Consistent response envelope (data, pagination)
发现
标签

此技能尚未发布标签。

版本
最新版本元数据

版本

v2026.09.24

发布时间

Sep 24, 2026

分类

未分类

许可证

MIT

源路径

skills/api-design/pagination

默认分支

main

最新提交

9496306

Tree SHA

fe4e2f1