minaremonshaker's avatar

Thoughts on Filtering Approach in Jeremy McPeak’s API Course

Hello, I’m currently taking the Jeremy McPeak course on building APIs. I’ve reached the 10th episode, where he demonstrates model filtering. However, I find his approach a bit complex and wonder why he didn’t create a dedicated service class for tickets. In that class, he could have implemented a filter method using the when() method to handle filtering logic for the ticket requests. https://laracasts.com/series/laravel-api-master-class/episodes/10

0 likes
5 replies
LaryAI's avatar
Level 58

Certainly! In Episode 10, Jeremy demonstrates filtering within the controller or directly on the Eloquent model, often resulting in multiple if conditions to customize queries based on HTTP request input. While this works, extracting filtering logic into a dedicated service class (often called a filter or query class) leads to a more maintainable and reusable codebase—especially as filtering grows in complexity.

Why Use a Service Class?

  • Separation of concerns: Keeps controllers slim.
  • Testability: Business logic is easier to test in isolation.
  • Reusability: Filtering logic can be reused elsewhere (web, API, jobs).

Example Approach

Let’s imagine a TicketFilter class which consolidates request-based filtering for Ticket queries:

Usage in Controller:

use App\Filters\TicketFilter;
use App\Models\Ticket;
use Illuminate\Http\Request;

public function index(Request $request, TicketFilter $filter)
{
    $tickets = $filter->apply(Ticket::query())->get();

    return response()->json($tickets);
}

Or, using the when() method directly for simple cases:

public function index(Request $request)
{
    $tickets = Ticket::query()
        ->when($request->filled('status'), fn($q) => $q->where('status', $request->status))
        ->when($request->filled('user_id'), fn($q) => $q->where('user_id', $request->user_id))
        ->get();

    return response()->json($tickets);
}

Conclusion: Jeremy’s inline approach is more direct for small APIs, but if you need scalable, DRY, and testable code, using a dedicated service/filter class is an excellent idea—your instinct is correct!

Let me know if you want an even more advanced example, such as using Laravel query scopes or packages like spatie/laravel-query-builder to automate and streamline these patterns further.

minaremonshaker's avatar

Hi Martin, is using Laravel's query builder methods like when() considered bad practice or does it impact application performance? I find query builders more readable and straightforward, though some packages add abstractions that complicate maintenance— what's your take? In a previous project with sorting, filtering, and searches, I used them directly in the service layer.

jlrdw's avatar

I have had no problems with regular sql statements like:

  • WHERE
  • ORDER BY
  • etc

Eloquent shortcuts convert to regular sql anyway.

minaremonshaker's avatar

this is one of my UserServise class method that i used in filtering and searching , it is huge but for me its readable and easy to maintain what do you think

    public static function listUsers(array $validatedData): array|LengthAwarePaginator|_IH_User_C
    {
        return User::query()
            ->select('users.first_name', 'users.last_name', 'users.email', 'users.username', 'users.id', 'users.locked')
            ->when(Arr::hasAny($validatedData, ['search', 'searchBy']), function (Builder $query) use ($validatedData) {
                $query->whereFullText($validatedData['searchBy'] ?? ['first_name', 'last_name', 'email', 'username'], "{$validatedData['search']}*", ['mode' => 'boolean']);
            })
            ->when(Arr::has($validatedData, 'filters') && Arr::has($validatedData, 'filters.locked'), function (Builder $query) use ($validatedData) {
                $query->where('locked', (int)Arr::get($validatedData, 'filters.locked'));
            })
            ->when(Arr::has($validatedData, 'filters') && Arr::hasAny($validatedData, ['filters.gender', 'filters.country', 'filters.city']), function (Builder $query) use ($validatedData) {
                 $query->whereHas('profile', function ($query) use ($validatedData) {
                     $query
                         ->whereCountry(Arr::get($validatedData, 'filters.country'))
                         ->orWhere('city', Arr::get($validatedData, 'filters.city'))
                         ->orwhere('gender', Arr::get($validatedData, 'filters.gender'));
                 });
            })
            ->with('profile:user_id,gender')
            ->orderBy($validatedData['orderBy'] ?? 'id', $validatedData['dir'] ?? 'asc')
            ->paginate($validatedData['per_page'] ?? 10)
            ->withQueryString();
    }

Please or to participate in this conversation.