abbasudo/laravel-purity
Laravel Purity adds elegant filtering and sorting to Eloquent queries via a simple filter() method. Let frontend users drive complex query conditions using URL query string parameters (e.g., filters[title][$contains]=Purity), with minimal boilerplate.
composer require abbasudo/laravel-purity
php artisan vendor:publish --tag=purity
use Abbasudo\Purity\Traits\Filterable;
use Abbasudo\Purity\Traits\Sortable;
class Post extends Model
{
use Filterable, Sortable;
}
return Post::filter()->sort()->get();
qs library to construct query strings:
const qs = require('qs');
const query = qs.stringify({
filters: { title: { $contains: 'Purity' } },
sort: ['title:asc']
});
await request(`/api/posts?${query}`);
Create a filtered/sorted API endpoint for a Post model:
// routes/api.php
Route::get('/posts', [PostController::class, 'index']);
// app/Http/Controllers/PostController.php
public function index()
{
return Post::filter()->sort()->paginate(10);
}
Now users can filter/sort via URL:
GET /api/posts?filters[title][$contains]=Laravel&sort[0]=created_at:desc
Basic Filtering/Sorting:
// Controller
$posts = Post::filter()->sort()->get();
// Frontend
const query = qs.stringify({
filters: { title: { $eq: 'Hello' } },
sort: ['title']
});
Livewire Integration:
// Component
#[Url]
public $filters = ['title' => []];
public function render()
{
$posts = Post::filter($this->filters)->get();
return view('livewire.posts', compact('posts'));
}
<!-- Blade -->
<input wire:model.live="filters.title.$eq" placeholder="Exact title">
Relation Filtering:
// Model
class Post extends Model
{
use Filterable;
public function tags() { return $this;hasMany(Tag::class); }
}
class Tag extends Model
{
use Filterable;
protected $filterFields = ['name'];
}
// Frontend
const query = qs.stringify({
filters: { 'tags.name': { $contains: 'Tech' } }
});
return PostResource::collection(Post::filter()->sort()->paginate());
FormRequest:
public function rules()
{
return [
'filters.title.$contains' => 'nullable|string|max:255',
'sort' => 'nullable|array',
];
}
return Cache::remember('filtered_posts', now()->addHour(), function() {
return Post::filter()->sort()->get();
});
Silent Exceptions:
config/purity.php:
'silent' => false,
app/Exceptions/Handler.php:
public function register()
{
$this->reportable(function (\Abbasudo\Purity\Exceptions\FilterException $e) {
Log::error($e);
});
}
Relation Fields:
Filterable trait and $filterFields defined.post.tags.user.name) are not supported.Field Renaming:
renameFields in config/purity.php to map API fields to DB columns:
'rename_fields' => [
'post_title' => 'title',
],
\DB::enableQueryLog();
Post::filter()->sort()->get();
dd(\DB::getQueryLog());
qs library):
const validOperators = ['$eq', '$contains', '$gt'];
if (!validOperators.includes(Object.keys(filters)[0])) {
throw new Error('Invalid filter operator');
}
Custom Filters:
Add custom filter methods in app/Providers/PurityServiceProvider.php:
public function boot()
{
\Abbasudo\Purity\Facades\Purity::extend('custom', function ($query, $value) {
return $query->where('column', 'like', "%{$value}%");
});
}
Usage:
filters: { title: { $custom: 'search' } }
Middleware: Restrict filters/sorts to authenticated users:
public function handle($request, Closure $next)
{
if (!$request->user()->can('filter_posts')) {
abort(403);
}
return $next($request);
}
Apply to routes:
Route::get('/posts', [PostController::class, 'index'])->middleware('can:filter_posts');
Testing: Mock filter/sort logic in tests:
$this->get('/api/posts?filters[title][$eq]=Test')
->assertOk()
->assertJsonCount(1);
How can I help you explore Laravel packages today?