Looking to hire Laravel developers? Try LaraJobs

laravel-api-query-helper maintained by iwastenot

Description
A helper class for building API queries in Laravel
Last update
2026/07/14 16:45 (0.3.0)
License
Downloads
0
Tags

Comments
comments powered by Disqus

iWasteNot API Query Helper

A structured query parser and Eloquent/Scout integration toolkit for Laravel APIs

The iWasteNot API Query Helper provides a consistent way to transform HTTP query parameters into robust Eloquent (or Laravel Scout) queries.
It standardizes filtering, sorting, field selection, and relationship inclusion for JSON APIs without requiring repetitive controller code.


🧱 Core Concept

Write API endpoints like:

GET /api/items?_with=photos&_fields=id,title,price&_sort=-created_at&price-gte=100&status-eq=active

and get this automatically:

Item::with(['photos'])
    ->select(['id', 'title', 'price'])
    ->where('price', '>=', 100)
    ->where('status', '=', 'active')
    ->orderBy('created_at', 'desc')
    ->get();

All query parsing, validation, and recursion are handled internally.


✅ Compatibility

Component Supported versions
PHP >= 7.4
Laravel / Illuminate 8.x, 9.x, 10.x, 11.x, 12.x

Notes:

  • When PHP is 7.4, only Laravel/Illuminate 8.x is supported.

⚡ Quick Start

composer install --no-interaction --prefer-dist
vendor/bin/phpunit

🚀 Installation

composer require iwastenot/laravel-api-query-helper

Then import the helper where you build your API queries:

use IWasteNot\ApiQueryHelper\Services\ApiQueryHelper;

class ItemController extends Controller
{
    public function index(Request $request, ApiQueryHelper $api)
    {
        $query = Item::query();

        $results = $api->indexHelper($query, $request->all())
            ->paginate();

        return new IndexCollection($results);
    }
}

📚 Documentation

  • docs/tutorials/getting-started.md — walkthrough from model to request
  • docs/how-to/filters.md — filter patterns + suffix list
  • docs/how-to/relations.md — nested _with, _fields, _sort, filters
  • docs/how-to/scout.md — Scout search and _q
  • docs/ApiQueryHelper.md — reference for helper methods
  • docs/ApiModel.md — reference for model helpers
  • docs/glossary.md — glossary of query terms
  • docs/faq.md — common questions and troubleshooting
  • docs/migration-notes.md — behavior changes between releases
  • docs/developer-guide.md — contributor setup notes

⚙️ Supported Parameters

Parameter Type Description
_with comma-separated list Eager-load relationships (with())
_fields comma-separated list Restrict selected columns
_sort comma-separated list Sort fields, prefix with - for descending; dotted paths (category-name) sort by related columns
_q string Model-directed search across the model's $searchable fields
_or[N][...] bracketed groups Or-groups: entries or-combine, groups and-combine with everything else
all others filters Translated into where*() clauses based on suffix

🔎 Filter Syntax

Filters are derived from all parameters except _with, _fields, _sort, _q, and _or. Keys that start with _ are ignored by the filter parser.

Example Effect Method
status-eq=active status = 'active' where()
price-gte=100 price >= 100 where()
deleted-null deleted IS NULL whereNull()
deleted-not-null deleted IS NOT NULL whereNotNull()
id-in=1,2,3 id IN (1,2,3) whereIn()
role-not-in=admin,staff role NOT IN (...) whereNotIn()
created-between=2024-01-01,2024-12-31 Date range whereBetween()
gte=price,100 price >= 100 where()
status=active status = 'active' where()
`title-lk=%a% %b%` title LIKE '%a%' OR title LIKE '%b%'
category-name-lk=%x% parent rows with a matching category whereHas()
_or[0][a-lk]=%x%&_or[0][b-lk]=%y% (a LIKE '%x%' OR b LIKE '%y%') nested where()

Supported suffixes

The helper supports these suffixes by default:

  • lk, like => where ... like
  • not-lk, not-like => where ... not like
  • gte => where ... >=
  • lte => where ... <=
  • gt => where ... >
  • lt => where ... <
  • eq => where ... =
  • not, not-eq => where ... !=
  • null => whereNull
  • not-null => whereNotNull
  • in => whereIn
  • not-in => whereNotIn
  • between => whereBetween
  • not-between => whereNotBetween
  • date => whereDate
  • year => whereYear
  • month => whereMonth
  • day => whereDay
  • time => whereTime
  • column => whereColumn

For null / not-null, the value list is ignored and no parameters are passed to the where method.

Suffixes and aliases are normalized via a configurable $suffixes map. In the suffix-as-key form (for example, gte=price,100), the first value is the field name and the remaining values are parameters for the where clause. In the suffix-in-key form, hyphens in the field portion are interpreted as dots for nested relations (for example, photos-filename-like=image targets photos.filename).


🧮 Eloquent Integration

Models intended for API use should extend the provided ApiModel class.

Example

use IWasteNot\ApiQueryHelper\Models\ApiModel;

class Item extends ApiModel
{
    protected $fillable = ['title', 'price', 'status'];
    protected $joinable = ['photos', 'tags'];
    protected $details = ['description', 'notes'];

    public function photos() { return $this->hasMany(Photo::class); }
    public function tags() { return $this->belongsToMany(Tag::class); }
}

This gives ApiQueryHelper introspection capabilities through:

  • getJoinable() — whitelist of relationships allowed in _with
  • getKeys() — all known safe fields
  • getIndexKeys() — indexable, visible subset
  • getIndexAttributes() — filtered attributes for display

💡 Usage Examples

Basic filtering, sorting, and field selection

GET /api/items?_fields=id,title,price&_sort=-created_at&price-lte=100&status-eq=active
Item::select(['id','title','price'])
    ->where('price', '<=', 100)
    ->where('status', '=', 'active')
    ->orderBy('created_at', 'desc')
    ->get();

Eager loading and nested filtering

GET /api/items?_with=photos,tags&photos.filename-like=image

→ Loads related photos where the filename matches "image".

Nested relation fields and sorts

GET /api/items?_with=photos&photos._fields=id,filename&photos._sort=-created_at

→ Scopes _fields and _sort to the photos relation.

Full-text search with Laravel Scout

$results = $api->indexHelper(Item::class, ['_q' => 'hammer']);

When you call indexHelper() with a model class name, Scout mode is used only when the params are limited to _q, per_page, and offset. The helper does not apply pagination itself; those values are for your controller/paginator.

_q searches the fields that your Scout driver indexes for the model. Define them by overriding toSearchableArray() and reindex after changes.

Custom prefiltering in models

public function indexHelperEloquent($query, $joins, $fields, $sorts, $filters)
{
    $filters['status'][] = ['=', 'active'];
    return [$query, $joins, $fields, $sorts, $filters];
}

See docs/ApiQueryHelper.md for extension-point details.

Show helper for single models

$item = Item::findOrFail($id);
$item = $api->showHelper($item, request()->all());

→ Loads _with relations without altering the base query.


🧩 Internal Structure

File Role
ApiQueryHelper.php Main query parser and orchestrator
Arr.php Array filtering utilities for dotted keys
ApiModel.php Base model with introspection hooks
IndexCollection.php (optional) Resource wrapper for paginated results

🧪 Testing

vendor/bin/phpunit

⚠️ Limitations

  • Scout filters are not whitelisted via getKeys().
  • Pagination is controlled by your controller (the helper does not paginate).

🩹 Troubleshooting

  • If Scout returns no results, confirm the driver is configured and the model index is built/rebuilt.
  • If filters are ignored, ensure the field is present in getKeys().
  • If nested filters do nothing, confirm the parent relation is in _with.

Changelog

See CHANGELOG.md for release notes and historical changes.


📄 License

MIT. See LICENSE.