spatie/laravel-query-builder
Build safe, flexible Eloquent queries from incoming API requests. Supports whitelisted filtering (partial/exact/scope/custom), sorting, includes, field selection, pagination, and grouped AND/OR filters—ideal for JSON:API-style endpoints with minimal boilerplate.
AllowedFilter, AllowedSort, and AllowedInclude, enabling fine-tuned security and validation per endpoint.Key Synergies:
with(), orderBy(), where()) under the hood.Sort).Potential Challenges:
| Risk Area | Mitigation |
|---|---|
| Query Injection | Strict whitelisting via allowedFilters()/allowedSorts(); no raw SQL exposure. |
| Performance Degradation | Profile with DB::enableQueryLog(); optimize indexes for filtered/sorted columns. |
| Version Lock-in | MIT license + active maintenance (last release: 2026); upgrade path documented. |
| Custom Logic Complexity | Interface-based extensions (e.g., Sort) allow controlled complexity. |
| Caching Invalidation | Cache keys must include query parameters (e.g., Cache::remember("users_{$request->query()}", ...)). |
Critical Questions:
fields[])? If yes, ensure database permissions align with allowed fields.posts.comments) for depth limits.ValidatesRequests or middleware to sanitize inputs before QueryBuilder.422 errors or default behaviors.HttpTests).QueryBuilder in resolver logic.where() chains with declarative allowedFilters().Anti-Patterns:
| Phase | Action Items | Tools/Dependencies |
|---|---|---|
| Assessment | Audit existing queries for dynamic parameters; identify high-impact endpoints. | telescope, DB::queryLog() |
| Pilot | Replace 1–2 endpoints with QueryBuilder; compare performance. |
Benchmark, Laravel Debugbar |
| Incremental Rollout | Wrap controllers/resources with QueryBuilder; update API docs. |
OpenAPI generator (e.g., darkaonline/l5-swagger) |
| Full Adoption | Standardize on QueryBuilder for all dynamic queries; deprecate legacy logic. |
PHPStan, Pest for validation tests |
Example Migration:
// Before: Manual filtering in controller
public function index(Request $request)
{
return User::query()
->when($request->has('name'), fn($q) => $q->where('name', 'like', "%{$request->name}%"))
->when($request->has('sort'), fn($q) => $q->orderBy($request->sort))
->get();
}
// After: Declarative with QueryBuilder
public function index(Request $request)
{
return QueryBuilder::for(User::class)
->allowedFilters('name')
->allowedSorts('name', 'created_at')
->get();
}
composer.json for constraints.spatie/laravel-activitylog (filter by activity).laravel-scout (filter by searchable attributes).Sequencing:
Table components).UserService::findByCriteria()).InvalidFilterQuery, etc.).QueryBuilder easily in unit tests (e.g., QueryBuilder::fake()).Maintenance Tasks:
| Task | Frequency | Tools |
|---|---|---|
| Update package dependencies | Quarterly | composer update + phpstan |
| Review allowed filters/sorts | Bi-annually | php artisan tinker (test queries) |
| Monitor query performance | Monthly | telescope, blackfire.io |
| Update API documentation | Per release | darkaonline/l5-swagger |
QueryBuilder::enableDumping() to log raw SQL.422 with allowed filters).Example Error Handling:
try {
return QueryBuilder::for(User::class)
->allowedFilters('name')
->get();
} catch (InvalidFilterQuery $e) {
return response()->json([
'message' => 'Invalid filter',
'allowed_filters' => ['name', 'email'],
], 422);
}
name for LIKE queries).->paginate() to avoid memory issues with large datasets.Cache::remember("users_{$request->query()}", ...)).laravel-shift/laravel-queues-tester.DB::connection()->getPdo()->getAvailableConnections() for connection pooling.laravel-horizon).Benchmarking:
// Test with 10K users
How can I help you explore Laravel packages today?