Laravel API Pagination, Dynamic Filtering & Performance Sorting

When an API database grows to hundreds of thousands of records, returning entire datasets crashes client applications and exhausts server memory. Production REST APIs require Pagination (chunking results into manageable pages), Dynamic Filtering (querying by status, date, or author), and Multi-Column Sorting (ordering by price, date, or popularity).

1. Standard Offset Pagination vs Cursor Pagination

Pagination Type Method Call SQL Mechanism Performance on 1 Million Rows
LengthAware (Offset) Article::paginate(15) LIMIT 15 OFFSET 50000 Slow on large offsets (requires full table scan)
Simple Pagination Article::simplePaginate(15) LIMIT 16 OFFSET 50000 Faster (Omits heavy SELECT count(*) query)
Cursor Pagination Article::cursorPaginate(15) WHERE id > 50000 LIMIT 15 Sub-millisecond constant speed! Ideal for infinite scrolls

2. Implementing Clean Dynamic Query Filtering

Avoid messy if ($request->has(...)) chains in your controller by using Eloquent's when() helper:

public function index(Request $request)
{
    $articles = Article::query()
        // 1. Text Search Filter
        ->when($request->filled('search'), function ($query) use ($request) {
            $query->where(function ($q) use ($request) {
                $q->where('title', 'like', "%{$request->search}%")
                  ->orWhere('body', 'like', "%{$request->search}%");
            });
        })
        // 2. Status Filter
        ->when($request->filled('status'), fn ($q) => $q->where('status', $request->status))
        // 3. Category Filter
        ->when($request->filled('category_id'), fn ($q) => $q->where('category_id', $request->category_id))
        // 4. Dynamic Sorting with Whitelist
        ->when($request->filled('sort'), function ($query) use ($request) {
            $sortColumn = ltrim($request->sort, '-');
            $direction = str_starts_with($request->sort, '-') ? 'desc' : 'asc';
            
            // Security: Strictly whitelist allowed sort columns
            if (in_array($sortColumn, ['title', 'created_at', 'views'])) {
                $query->orderBy($sortColumn, $direction);
            }
        }, fn ($query) => $query->latest()) // Default sort
        ->cursorPaginate(20);

    return ArticleResource::collection($articles);
}

3. Enterprise Filtering with Spatie Query Builder

For enterprise APIs, the gold standard is spatie/laravel-query-builder. It supports standard JSON:API specifications (e.g. GET /api/v1/articles?filter[status]=published&sort=-created_at&include=author) in two lines of code:

use Spatie\QueryBuilder\QueryBuilder;
use Spatie\QueryBuilder\AllowedFilter;

$articles = QueryBuilder::for(Article::class)
    ->allowedFilters(['title', AllowedFilter::exact('status'), 'category_id'])
    ->allowedSorts(['title', 'created_at', 'price'])
    ->allowedIncludes(['author', 'comments'])
    ->paginate(15);

Frequently Asked Questions (FAQ) - Student & Interview Review

Q1. Why is Cursor Pagination preferred for mobile apps with infinite scrolling?

Offset pagination suffers from data shifting: if a new record is added while the user scrolls, the next page may show duplicate items. Cursor pagination uses index comparisons (where id < last_seen_id), guaranteeing zero duplicates and zero skipped rows.

Q2. What query parameters does cursorPaginate() generate in links?

It generates an encoded 'cursor' parameter (?cursor=eyJpZCI6MTB9) containing the indexed column position of the last record.

Q3. How do I let clients customize the per_page limit safely?

$perPage = min($request->integer('per_page', 15), 100);. Always clamp the maximum limit using min() to prevent clients from requesting ?per_page=1000000 and exhausting server RAM.

Explore Related Laravel Tutorials & Next Steps

Broaden your backend architecture, REST API design, and enterprise production engineering expertise:

RESTful API Architecture

Design clean query parameter standards (?sort=-created_at&filter[status]=active).

High-Performance Redis Caching

Cache paginated API responses to reduce database load during traffic spikes.

Fluent Query Builder Aggregates

Execute performant database count queries and cursor-based offsets.

15+ Hands-on Practice Labs

Build an API catalog filter with multiple parameters and pagination controls.

Share Share on Facebook Share on Twitter Share on LinkedIn Pin on Pinterest Share on Reddit

You may also like this!