Query Builder
The QueryBuilder is the main entry point for building API endpoints. It ties together filtering, sorting, includes, and pagination into a single fluent API.
Basic Usage
use App\Http\Resources\ProductResource;
use App\Models\Product;
use BlueBeetle\ApiToolkit\QueryBuilder;
use Illuminate\Http\Request;
class ProductController
{
public function index(Request $request)
{
return QueryBuilder::for(Product::class, $request)
->fromResource(ProductResource::class)
->paginate();
}
}
This single call handles:
- Parsing
?filter[name]=widgetand applying allowed filters - Parsing
?sort=-created_at,nameand applying allowed sorts - Parsing
?include=category,tagsand eager loading relationships - Parsing
?page[number]=2&page[size]=20and paginating
From a Resource
The fromResource() method pulls filter, sort, and include configuration from your resource class:
use BlueBeetle\ApiToolkit\Parsers\Filters\ExactFilter;
use BlueBeetle\ApiToolkit\Parsers\Filters\PartialFilter;
use BlueBeetle\ApiToolkit\Parsers\Filters\ScopeFilter;
use BlueBeetle\ApiToolkit\Resources\Resource;
class ProductResource extends Resource
{
public function allowedFilters(): array
{
return [
'name' => new PartialFilter(),
'status' => new ExactFilter(),
'category' => new ScopeFilter(),
];
}
public function allowedSorts(): array
{
return ['name', 'price', 'created_at'];
}
public function defaultSort(): ?string
{
return '-created_at';
}
public function allowedIncludes(): array
{
return ['category', 'tags'];
}
}
Then in your controller, fromResource() picks up all of these automatically.
Overriding Resource Configuration
Method calls on the QueryBuilder take priority over resource definitions. If you call allowedFilters(), allowedSorts(), defaultSort(), or allowedIncludes() on the builder, those values replace whatever the resource defines. This lets you reuse the same resource class across endpoints while customizing behavior per route.
use App\Http\Resources\ProductResource;
use App\Models\Product;
use BlueBeetle\ApiToolkit\Parsers\Filters\ExactFilter;
use BlueBeetle\ApiToolkit\QueryBuilder;
use Illuminate\Http\Request;
class ProductController
{
public function index(Request $request)
{
return QueryBuilder::for(Product::class, $request)
->fromResource(ProductResource::class)
->allowedFilters(['name' => new ExactFilter()]) // overrides resource filters
->allowedSorts(['name']) // overrides resource sorts
->defaultSort('name') // overrides resource default
->paginate();
}
}
From an Existing Builder
Pass an Eloquent builder instead of a model class to start with a scoped query:
use App\Http\Resources\ProductResource;
use App\Models\Product;
use BlueBeetle\ApiToolkit\QueryBuilder;
use Illuminate\Http\Request;
class ProductController
{
public function active(Request $request)
{
$activeProducts = Product::where('is_active', true);
return QueryBuilder::for($activeProducts, $request)
->fromResource(ProductResource::class)
->paginate();
}
}
Pagination Methods
// Offset pagination: ?page[number]=2&page[size]=20
->paginate()
// Cursor pagination: ?page[cursor]=eyJpZCI6MTB9&page[size]=20
->cursor()
// No pagination, returns all results
->get()
Apply Without Fetching
Use apply() to apply filters, sorts, and includes without executing the query. This returns the QueryBuilder instance, so you can call getQuery() to access the underlying Eloquent builder. This is useful when you need the modified query for something other than a standard response.
Counting Results
use App\Http\Resources\ProductResource;
use App\Models\Product;
use BlueBeetle\ApiToolkit\QueryBuilder;
use Illuminate\Http\Request;
class ProductController
{
public function count(Request $request)
{
$builder = QueryBuilder::for(Product::class, $request)
->fromResource(ProductResource::class)
->apply();
$query = $builder->getQuery();
return response()->json(['count' => $query->count()]);
}
}
Exporting to CSV
use App\Http\Resources\ProductResource;
use App\Models\Product;
use BlueBeetle\ApiToolkit\QueryBuilder;
use Illuminate\Http\Request;
class ProductExportController
{
public function __invoke(Request $request)
{
$query = QueryBuilder::for(Product::class, $request)
->fromResource(ProductResource::class)
->apply()
->getQuery();
return response()->streamDownload(function () use ($query) {
$query->each(function ($product) {
echo $product->name . ',' . $product->price . "\n";
});
}, 'products.csv');
}
}
Custom Response Format
use App\Http\Resources\ProductResource;
use App\Models\Product;
use BlueBeetle\ApiToolkit\QueryBuilder;
use Illuminate\Http\Request;
class ProductStatsController
{
public function __invoke(Request $request)
{
$query = QueryBuilder::for(Product::class, $request)
->fromResource(ProductResource::class)
->apply()
->getQuery();
return response()->json([
'total' => $query->count(),
'avg_price' => $query->avg('price'),
'max_price' => $query->max('price'),
]);
}
}
The getQuery() Method
getQuery() returns the underlying Illuminate\Database\Eloquent\Builder instance. You can call it at any time, but it is most useful after apply() so the builder already has all filters, sorts, and includes applied. The returned builder is a standard Eloquent builder, so you can chain any Eloquent method on it.