Skip to content

Pagination

Efficiently paginate large result sets with built-in support.

Basic Pagination

val result: PagedResult<Product>? = konduct.collection<Product>()
    .match { Product::status eq "active" }
    .sort { Product::name.asc() }
    .paginate(page = 0, pageSize = 20)
    .firstOrNull()

PagedResult Structure

data class PagedResult<T>(
    val data: List<T>,        // Current page items
    val total: Long,          // Total matching documents
    val page: Int,            // Current page (0-based)
    val pageSize: Int,        // Items per page
    val totalPages: Int       // Total number of pages
)

Accessing Results

result?.let {
    println("Showing ${it.data.size} of ${it.total} products")
    println("Page ${it.page + 1} of ${it.totalPages}")

    it.data.forEach { product ->
        println(product.name)
    }
}

With Filters and Sorting

fun searchProducts(
    query: String,
    category: String?,
    page: Int,
    pageSize: Int
): PagedResult<Product> {
    return konduct.collection<Product>()
        .match {
            Product::name regex query.toRegex(RegexOption.IGNORE_CASE)
            Product::status eq "active"
            category?.let { Product::category eq it }
        }
        .sort { Product::rating.desc() }
        .paginate(page, pageSize)
        .firstOrNull() ?: PagedResult(
            data = emptyList(),
            total = 0,
            page = page,
            pageSize = pageSize,
            totalPages = 0
        )
}

REST API Example

@RestController
@RequestMapping("/api/products")
class ProductController(private val productService: ProductService) {

    @GetMapping
    fun getProducts(
        @RequestParam(defaultValue = "0") page: Int,
        @RequestParam(defaultValue = "20") size: Int,
        @RequestParam(required = false) category: String?
    ): PagedResult<Product> {
        return productService.getProducts(page, size, category)
    }
}

Response:

{
  "data": [
    { "id": "1", "name": "Product 1", "price": 99.99 },
    { "id": "2", "name": "Product 2", "price": 149.99 }
  ],
  "total": 150,
  "page": 0,
  "pageSize": 20,
  "totalPages": 8
}

Paginating Grouped Results

val result = konduct.collection<Order>()
    .match { Order::status eq "completed" }
    .group {
        by(Order::customerId)
        accumulate {
            "totalSpent" sum Order::total
            "orderCount" count Unit
        }
    }
    .sort { "totalSpent".desc() }
    .paginate(page = 0, pageSize = 10)
    .firstOrNull()

Performance Considerations

DO: Filter Before Paginating

// ✅ Good - filter first
konduct.collection<Product>()
    .match { Product::category eq "Electronics" }  // Reduces dataset
    .paginate(page, pageSize)

DON'T: Paginate Everything

// ❌ Bad - paginating millions of documents
konduct.collection<Product>()
    .paginate(page, pageSize)  // No filter!

Use Indexes

@Document
@CompoundIndex(def = "{'status': 1, 'createdAt': -1}")
data class Product(
    @Indexed val status: String,
    val createdAt: Date
)

Manual Pagination

If you need custom pagination:

konduct.collection<Product>()
    .match { Product::status eq "active" }
    .sort { Product::createdAt.desc() }
    .skip(page * pageSize)
    .limit(pageSize)
    .toList()

// Get total separately
val total = konduct.collection<Product>()
    .match { Product::status eq "active" }
    .count()

Cursor-Based Pagination (Alternative)

For very large datasets, use cursor-based pagination:

fun getProductsAfter(
    lastId: String?,
    limit: Int = 20
): List<Product> {
    return konduct.collection<Product>()
        .match {
            Product::status eq "active"
            lastId?.let { Product::id gt it }
        }
        .sort { Product::id.asc() }
        .limit(limit)
        .toList()
}

See Also