Skip to content

Pagination

When your application deals with large amounts of data—hundreds of users, thousands of products, millions of log entries—you can’t just send it all to the client at once. Doing so would be incredibly slow, wasteful of bandwidth, and overwhelming for the user. The solution to this universal problem is pagination: breaking up a large dataset into smaller, manageable “pages.”

SliceFlow has a powerful, elegant, and consistent system for handling pagination built into its core, making it incredibly simple to build high-performance APIs that can handle massive amounts of data with ease.

A Conversation Between the Client and Server

At its heart, SliceFlow’s pagination is a simple conversation. The client asks for a specific page of data, and the server responds not only with that page but also with all the information the client needs to understand its place in the larger dataset.

The Client’s Request

When a client wants to fetch a list of items, it can add two simple parameters to its request URL:

  • pageNumber: Which page do you want? (e.g., ?pageNumber=2)
  • pageSize: How many items should be on that page? (e.g., &pageSize=10)

SliceFlow captures this in a reusable PaginatedRequest object. To make life easy, it comes with sensible defaults: if the client doesn’t specify, it assumes they want pageNumber=1 and pageSize=25.

To keep your API robust, this request is automatically validated. SliceFlow ensures that clients can’t ask for a negative page number or request an unreasonable number of items at once (the default limit is 100 per page), preventing potential abuse and performance issues.

The Server’s Response

In return, the server provides a structured PaginatedResponse. This response is designed to give a frontend application everything it needs to build a beautiful and functional pagination UI (like “Page 3 of 15” with “Next” and “Previous” buttons).

The response includes:

  • data: The actual list of items for the requested page.
  • pageNumber: The page number that is being returned.
  • pageSize: The number of items on this page.
  • totalPages: The total number of pages available.
  • totalCount: The total number of items in the entire collection.

A typical JSON response would look like this:

{
"data": [
{ "id": "...", "email": "user1@example.com" },
{ "id": "...", "email": "user2@example.com" }
],
"pageNumber": 1,
"pageSize": 10,
"totalPages": 15,
"totalCount": 142
}

The Magic: One Line to Paginate Anything

The best part of SliceFlow’s pagination system is how effortless it is to implement. Thanks to a powerful extension method, ToPaginatedResultAsync, the complex logic of counting total records, calculating total pages, and fetching the correct subset of data from the database is handled in a single, readable line of code.

This method is smart: it works directly with IQueryable, which means it translates your request into efficient SQL. It will only fetch the exact 10 or 25 items you need from the database, rather than pulling thousands of records into memory first.

Putting It All Together: Listing Users

Let’s look at a real-world example: an endpoint to list all users in the system.

1. Define the Request: The endpoint’s request simply inherits from the base PaginatedRequest.

public record ListAllRequest : PaginatedRequest;

2. Implement the Endpoint: The endpoint logic is incredibly concise. It gets a query for users from the database and then uses the magic extension method to generate the complete, paginated response.

internal sealed class ListAll(DataContext db) : Endpoint<ListAllRequest, Ok<PaginatedResponse<ListAllResponse>>>
{
public override void Configure()
{
Get("/");
Version(1);
Group<EndpointGroup.User>();
Permissions(Allow.User.ListAll);
}
public override async Task<Ok<PaginatedResponse<ListAllResponse>>> ExecuteAsync(ListAllRequest req, CancellationToken ct)
{
// Get the base query from the database
var usersQuery = db.Users.AsQueryable();
// One line does all the work!
var paginatedResult = await usersQuery.ToPaginatedResultAsync(
req.PageNumber,
req.PageSize,
user => new ListAllResponse(user.Id, user.Email), // Map to a response object
ct);
return TypedResults.Ok(paginatedResult);
}
}

And that’s it. With just a few lines of code, you have a secure, performant, and fully-featured paginated API endpoint that is consistent with the rest of your SliceFlow application.