asseco-voice/laravel-json-query-builder
Build complex Eloquent queries from JSON with asseco-voice/laravel-json-query-builder. Turn structured JSON payloads into safe, readable filters and sorts, making API-driven search and dynamic filtering simple in Laravel.
Architecture fit. The package is an adapter between an inbound JSON payload and Laravel's Eloquent query builder — effectively a user-facing query DSL. It fits cleanly when you have many read endpoints with ad-hoc filtering/sorting/pagination needs and a thin controller layer: the JSON document becomes a query DTO, the controller hands it to the builder, and API Resources serialize the result. It is a poor fit if your API contracts are strict and versioned (you would be exposing a third-party library's DSL as your public schema), or if your hot read paths need hand-tuned SQL. Architecturally it should sit behind a port in your code — controllers should not import the vendor class directly — otherwise the library is your API contract and swapping it later is a breaking change.
Integration feasibility. Low ceremony: Composer install, publish config, call from a controller. The real feasibility question is not effort but compatibility. With a last release in March 2024 and 24 stars, Laravel 11/12 and PHP 8.3+ support must be verified from composer.json constraints, not assumed. A one-day spike against your actual Laravel version and database engine (MySQL vs Postgres differ on case sensitivity in LIKE and JSON column operators) will settle it.
Technical risk. The dominant risk is security, not stability. User-supplied JSON reaching a query builder creates the equivalent of mass assignment at the query layer: unless the package supports strict column/operator/relation allowlists, a client can filter on password_hash, sort by an unindexed column, or traverse relations into another tenant's rows. Second is complexity-based denial of service — unbounded filter counts, relation depth, and LIKE '%…%' patterns can generate expensive SQL with no package-level guardrail. Third is abandonment: low adoption (24 stars, score 1.16) means assume no upstream roadmap. Fourth is observability — the emitted SQL is opaque unless you log it, and you cannot optimize what you cannot see.
Key questions.
whereHas, joins, or eager loads — and can they be scoped to the authenticated tenant?Stack fit. Any Laravel API backed by Eloquent, paired with FormRequests for shape validation, API Resources for serialization, and Sanctum/policy checks applied before the builder is invoked. The package should never be the authorization layer.
Migration path. Introduce it behind an internal QueryFilter interface so controllers depend on your port, not the vendor namespace. Pilot on one internal, low-traffic read endpoint. Wrap the entry point with a strict allowlist config (fields, operators, relations, max page size, max depth) and a JSON Schema validator. Keep a thin fallback implementation — a hand-rolled query object or Spatie Query Builder — behind the same interface, so the decision remains reversible after two sprints of telemetry.
Compatibility. Verify Composer constraints before anything else; if the package predates your Laravel major, budget for a fork-and-patch or exclude it. Test against every DB engine in your fleet, since string comparison semantics and JSON operators differ. Check how it handles soft deletes, global scopes, and composite keys — those are common leak points in filter libraries.
Sequencing. (1) One-day spike in a sandbox branch against your real schema. (2) Security review and allowlist/JSON-Schema design — treat the JSON shape as a versioned public contract. (3) Internal-only endpoint with query logging enabled. (4) Add complexity caps, timeouts, and index coverage for every sortable field. (5) Roll to public read endpoints one resource at a time. (6) Build-vs-buy review after telemetry, with the fork cost already known.
Maintenance. Assume you own the code. Low activity and a single-vendor origin mean no guaranteed patches; plan to vendor-pin, monitor advisories, and budget one to two engineer-days per quarter for version bumps. Characterize it internally as an acquired open-source component with an owner, not as a dependency you consume passively.
Support. No commercial SLA; GitHub issues only. Stand up an internal runbook covering "query is slow," "query returns wrong rows," and "package breaks on framework upgrade." Name a single owner on the read-path team.
Scaling. The package does not scale or fail to scale — the queries clients send do. Enforce page-size ceilings, filter-count and relation-depth caps, statement timeouts, and mandatory indexes on every filterable/sortable column. Watch for N+1 across eager-loaded relations and for LIKE-prefix scans. Load growth manifests as database pressure, not package pressure, but a single malformed client payload becomes your incident.
Failure modes. Malformed JSON producing 500s instead of 422s; unknown fields silently dropped, causing clients to believe a filter applied; relation traversal crossing tenant boundaries; deep nesting causing statement timeouts; ambiguous column errors once joins appear; silent semantic changes on a patch bump. Each needs an explicit test.
Ramp-up. A mid-level Laravel developer is productive in a day; the harder cost is owning the DSL contract. Budget two to three days for the first integration including hardening, and add three artifacts to onboarding: the allowlist config, the JSON contract doc with worked examples, and the query-log review procedure. Given the adoption signal, the defensible position is a bounded pilot behind an interface with an exit already coded — not a platform-wide commitment.
How can I help you explore Laravel packages today?