Master advanced filtering, sorting, and pagination in Laravel to build scalable APIs. Learn to parse query params and implement dynamic Eloquent queries today.
Previously in this course, we explored Advanced Eloquent Scopes and Accessors: Cleaner Laravel Models to keep our database logic expressive. In this lesson, we are taking that foundation and applying it to our API layer, specifically addressing how to handle filtering, sorting, and pagination dynamically.
When building a project board API, you eventually reach a point where clients need to search for tasks by status, sort them by priority, or fetch them in manageable chunks. Hard-coding these paths is a maintenance nightmare. Instead, we’ll build a dynamic query pipeline.
If you’ve ever seen a controller method with a dozen if ($request->has(...)) statements, you know the pain. Not only is it hard to read, but it’s also impossible to reuse.
To solve this, we will use a "Pipeline" approach. We want our controller to look like this:
PHPpublic function index(Request $request) { $tasks = $this->taskService->search($request->all()); return TaskResource::collection($tasks); }
By delegating the logic to the TaskService (following our work in Implementing the Service Layer), we keep our API endpoints thin and maintainable.
We’ll leverage the Eloquent scopes we defined earlier. To make this dynamic, we map incoming request keys to specific scope methods.
PHP#6A9955">// Inside TaskRepository.php public function applyFilters(Builder $query, array $filters) { #6A9955">// Map request keys to our model scopes if (isset($filters['status'])) { $query->whereStatus($filters['status']); } if (isset($filters['priority'])) { $query->wherePriority($filters['priority']); } return $query; }
Sorting is often dangerous if you simply pass user input directly into orderBy(), as it opens the door to SQL injection or exposing sensitive columns. Always use an allow-list.
PHPpublic function applySorting(Builder $query, string $sort = null) { $allowedSorts = ['created_at', 'priority', 'due_date']; #6A9955">// Default to created_at if not provided or invalid $column = in_array($sort, $allowedSorts) ? $sort : 'created_at'; return $query->orderBy($column, 'desc'); }
Laravel’s paginate() method is the standard for API responses. By combining our filters and sorting before calling paginate, we ensure the query is executed efficiently by the database rather than in memory.
PHP#6A9955">// TaskRepository.php public function getFilteredTasks(array $params) { $query = Task::query(); $this->applyFilters($query, $params); $this->applySorting($query, $params['sort'] ?? null); return $query->paginate($params['per_page'] ?? 15); }
In our running project board, let's update the TaskService to handle these incoming parameters.
PHP#6A9955">// TaskService.php public function listTasks(array $data) { #6A9955">// We pass the data directly to the repository #6A9955">// The repository handles the heavy lifting of Eloquent composition return $this->taskRepository->getFilteredTasks($data); }
This approach allows your API to handle requests like GET /api/tasks?status=in-progress&sort=priority&per_page=50 effortlessly.
TaskRepository, create a constant ALLOWED_SORT_COLUMNS to hold the fields users are permitted to sort by.TaskController logic into the TaskRepository using the pattern shown above.?status=completed&sort=due_date and asserts that the returned JSON collection is correctly filtered and sorted.per_page value. Allowing users to fetch thousands of records without limit will eventually crash your memory or slow your database to a crawl.$request->get('sort') directly into an orderBy() call. Always validate it against an allow-list.JsonResource collection, ensure you are eager loading relationships (like user or project) within your repository using $query->with(['user']) before calling paginate().We’ve evolved from manual request parsing to a clean, service-oriented approach. By centralizing filtering, sorting, and pagination in the repository layer, we’ve made our API more resilient and significantly easier to test. Remember: keep your controllers thin, validate your sorting keys, and always paginate your results.
Up next: We will implement Job Chaining and Batching to handle complex, multi-step background processes for our project tasks.
Master advanced Eloquent scopes to encapsulate complex business logic, chain query filters, and maintain clean, expressive models in your Laravel SaaS platform.
Read moreFinalize your Task Manager CRUD functionality by implementing secure edit and delete features. Learn how to maintain data integrity in your Laravel application.
Advanced Request Filtering and Sorting