Trung BìnhNestJS iconNestJS

Pagination trong NestJS — Cursor-based vs Offset-based?

Offset-based (skip/take): đơn giản, hỗ trợ random page access:

typescript
async findAll(page: number, limit: number) {
  const [data, total] = await this.repo.findAndCount({
    take: limit, skip: (page - 1) * limit,
    order: { createdAt: 'DESC' },
  });
  return { data, total, page, lastPage: Math.ceil(total / limit) };
}

Nhược điểm: không ổn định khi data thay đổi real-time, chậm khi skip lớn (DB phải scan).

Cursor-based (keyset pagination): ổn định hơn cho real-time feeds, hiệu quả hơn khi scale:

typescript
async findAfterCursor(cursor: string, limit: number) {
  const decodedCursor = Buffer.from(cursor, 'base64').toString();
  // cursor = ISO timestamp của item cuối cùng
  const data = await this.repo.find({
    where: { createdAt: LessThan(new Date(decodedCursor)) },
    take: limit + 1,  // +1 để biết có page tiếp không
    order: { createdAt: 'DESC' },
  });
  const hasMore = data.length > limit;
  const items = hasMore ? data.slice(0, -1) : data;
  const nextCursor = hasMore ? Buffer.from(items.at(-1)!.createdAt.toISOString()).toString('base64') : null;
  return { items, nextCursor, hasMore };
}

Dùng offset cho: admin dashboards, search results.

Dùng cursor cho: social feeds, infinite scroll.

Xem toàn bộ NestJS cùng filter theo level & chủ đề con.

Mở danh sách NestJS