SyntaxStudy
Sign Up
REST API Filtering, Sorting, and Pagination
REST API Beginner 1 min read

Filtering, Sorting, and Pagination

Collection endpoints should support filtering, sorting, and pagination via query parameters. Filtering narrows results by field values; sorting orders them; pagination returns a manageable chunk. These three concerns are independent and can be combined freely. Cursor-based pagination is preferred for large, frequently-updated datasets because it remains stable when rows are inserted or deleted between pages. Offset pagination is simpler but can skip or duplicate items on busy collections. Always include pagination metadata in the response so clients know when they have reached the last page.
Example
# Query string conventions
# GET /products?status=active&category=shoes&minPrice=50&maxPrice=200
# GET /products?sort=price&order=desc
# GET /products?page=3&perPage=25               (offset-based)
# GET /products?cursor=eyJpZCI6MTAwfQ&limit=25  (cursor-based)

# Laravel implementation with Spatie QueryBuilder
use Spatie\QueryBuilder\QueryBuilder;
use Spatie\QueryBuilder\AllowedFilter;

public function index(Request $request): JsonResponse {
    $products = QueryBuilder::for(Product::class)
        ->allowedFilters([
            'status',
            'category.name',
            AllowedFilter::scope('price_between'),
        ])
        ->allowedSorts(['price', 'name', 'created_at'])
        ->allowedIncludes(['category', 'images'])
        ->paginate($request->input('perPage', 20));

    return response()->json([
        'data' => $products->items(),
        'meta' => [
            'currentPage' => $products->currentPage(),
            'perPage'     => $products->perPage(),
            'total'       => $products->total(),
            'lastPage'    => $products->lastPage(),
        ],
        'links' => [
            'first' => $products->url(1),
            'last'  => $products->url($products->lastPage()),
            'prev'  => $products->previousPageUrl(),
            'next'  => $products->nextPageUrl(),
        ],
    ]);
}