6 hours ago
Laravel Query Builder v7: a must-have package for building APIs in Laravel
We just released v7 of [laravel-query-builder](https://github.com/spatie/laravel-query-builder), our package that makes it easy to build flexible API endpoints. If you're building an API with Laravel, you'll almost certainly need to let consumers filter results, sort them, include relationships and select specific fields. Writing that logic by hand for every endpoint gets repetitive fast, and it's easy to accidentally expose columns or relationships you didn't intend to.
Our query builder takes care of all of that. It reads query parameters from the URL, translates them into the right Eloquent queries, and makes sure only the things you've explicitly allowed can be queried.
```php
// GET /users?filter[name]=John&include=posts&sort=-created_at
$users = QueryBuilder::for(User::class)
->allowedFilters('name')
->allowedIncludes('posts')
->allowedSorts('created_at')
->get();
// select * from users where name = 'John' order by created_at desc
```
This major version requires PHP 8.3+ and Laravel 12 or higher, and brings a cleaner API along with some features we've been wanting to add for a while.
Let me walk you through how the package works and what's new.
<!--more-->
## Using the package
The idea is simple: your API consumers pass query parameters in the URL, and the package translates those into the right Eloquent query. You just define what's allowed.
Say you have a `User` model and you want to let API consumers filter by name. Here's all you need:
```php
use Spatie\QueryBuilder\QueryBuilder;
$users = QueryBuilder::for(User::class)
->allowedFilters('name')
->get();
```
Now when someone requests `/users?filter[name]=John`, the package adds the appropriate `WHERE` clause to the query:
```sql
select * from users where name = 'John'
```
Only the filters you've explicitly allowed will work. If someone tries `/users?filter[secret_column]=something`, the package throws an `InvalidFilterQuery` exception. Your database schema stays hidden from API consumers.
You can allow multiple filters at once and combine them with sorting:
```php
$users = QueryBuilder::for(User::class)
->allowedFilters('name', 'email')
->allowedSorts('name', 'created_at')
->get();
```
A request to `/users?filter[name]=John&sort=-created_at` now filters by name and sorts by `created_at` descending (the `-` prefix means descending).
Including relationships works the same way. If you want consumers to be able to eager-load a user's posts:
```php
$users = QueryBuilder::for(User::class)
->allowedFilters('name', 'email')
->allowedIncludes('posts', 'permissions')
->allowedSorts('name', 'created_at')
->get();
```
A request to `/users?include=posts&filter[name]=John&sort=-created_at` now returns users named John, sorted by creation date, with their posts eager-loaded.
You can also select specific fields to keep your responses lean:
```php
$users = QueryBuilder::for(User::class)
->allowedFields('id', 'name', 'email')
->allowedIncludes('posts')
->get();
```
With `/users?fields=id,email&include=posts`, only the `id` and `email` columns are selected.
The `QueryBuilder` extends Laravel's default Eloquent builder, so all your favorite methods still work. You can combine it with existing queries:
```php
$query = User::where('active', true);
$users = QueryBuilder::for($query)
->withTrashed()
->allowedFilters('name')
->allowedIncludes('posts', 'permissions')
->where('score', '>', 42)
->get();
```
The query parameter names follow the [JSON API specification](http://jsonapi.org/) as closely as possible. This means you get a consistent, well-documented API surface without having to think about naming conventions.
## What's new in v7
### Variadic parameters
All the `allowed*` methods now accept variadic arguments instead of arrays.
```php
// Before (v6)
QueryBuilder::for(User::class)
->allowedFilters(['name', 'email'])
->allowedSorts(['name'])
->allowedIncludes(['posts']);
// After (v7)
QueryBuilder::for(User::class)
->allowedFilters('name', 'email')
->allowedSorts('name')
->allowedIncludes('posts');
```
If you have a dynamic list, use the spread operator:
```php
$filters = ['name', 'email'];
QueryBuilder::for(User::class)->allowedFilters(...$filters);
```
### Aggregate includes
This is the biggest new feature. You can now include aggregate values for related models using `AllowedInclude::min()`, `AllowedInclude::max()`, `AllowedInclude::sum()`, and `AllowedInclude::avg()`. Under the hood, these map to Laravel's `withMin()`, `withMax()`, `withSum()` and `withAvg()` methods.
```php
use Spatie\QueryBuilder\AllowedInclude;
$users = QueryBuilder::for(User::class)
->allowedIncludes(
'posts',
AllowedInclude::count('postsCount'),
AllowedInclude::sum('postsViewsSum', 'posts', 'views'),
AllowedInclude::avg('postsViewsAvg', 'posts', 'views'),
)
->get();
```
A request to `/users?include=posts,postsCount,postsViewsSum` now returns users with their posts, the post count, and the total views across all posts.
You can constrain these aggregates too. For example, to only count published posts:
```php
use Spatie\QueryBuilder\AllowedInclude;
use Illuminate\Database\Eloquent\Builder;
$users = QueryBuilder::for(User::class)
->allowedIncludes(
AllowedInclude::count(
'publishedPostsCount',
'posts',
fn (Builder $query) => $query->where('published', true)
),
AllowedInclude::sum(
'publishedPostsViewsSum',
'posts',
'views',
constraint: fn (Builder $query) => $query->where('published', true)
),
)
->get();
```
All four aggregate types support these constraint closures, making it possible to build endpoints that return computed data alongside your models without writing custom query logic.
## A perfect match for Laravel's JSON:API resources
Laravel 13 is adding built-in support for [JSON:API resources](https://laravel.com/docs/master/eloquent-resources#jsonapi-resources). These new `JsonApiResource` classes handle the serialization side: they produce responses compliant with the JSON:API specification.
You create one by adding the `--json-api` flag:
```bash
php artisan make:resource PostResource --json-api
```
This generates a resource class where you define attributes and relationships:
```php
use Illuminate\Http\Resources\JsonApi\JsonApiResource;
class PostResource extends JsonApiResource
{
public $attributes = [
'title',
'body',
'created_at',
];
public $relationships = [
'author',
'comments',
];
}
```
Return it from your controller, and Laravel produces a fully compliant JSON:API response:
```json
{
"data": {
"id": "1",
"type": "posts",
"attributes": {
"title": "Hello World",
"body": "This is my first post."
},
"relationships": {
"author": {
"data": { "id": "1", "type": "users" }
}
}
},
"included": [
{
"id": "1",
"type": "users",
"attributes": { "name": "Taylor Otwell" }
}
]
}
```
Clients can request specific fields and includes via query parameters like `/api/posts?fields[posts]=title&include=author`. Laravel's JSON:API resources handle all of that on the response side.
The [Laravel docs](https://laravel.com/docs/master/eloquent-resources#jsonapi-resources) explicitly mention our package as a companion:
> Laravel's JSON:API resources handle the serialization of your responses. If you also need to parse incoming JSON:API query parameters such as filters and sorts, Spatie's Laravel Query Builder is a great companion package.
So while Laravel's new JSON:API resources take care of the output format, our query builder handles the input side: parsing `filter`, `sort`, `include` and `fields` parameters from the request and translating them into the right Eloquent queries. Together they give you a full JSON:API implementation with very little boilerplate.
## In closing
To upgrade from v6, check the [upgrade guide](https://github.com/spatie/laravel-query-builder/blob/main/UPGRADING.md). The changes are mostly mechanical. Check the guide for the full list.
You can find the full source code and documentation [on GitHub](https://github.com/spatie/laravel-query-builder). We also have extensive [documentation](https://spatie.be/docs/laravel-query-builder/v7/introduction) on the Spatie website.
This is one of the many packages we've created at [Spatie](https://spatie.be/open-source). If you want to support our open source work, consider picking up one of our [paid products](https://spatie.be/products).
🌟 Laravel Query Builder v7: a must-have package for building APIs in Laravel
#php #laravel #package #spatie #PHP
1
0
0
0