cerbero/lazy-json-pages
Cerbero LazyJsonPages lets you consume paginated JSON APIs as Laravel lazy collections. Point it at an endpoint, describe how pagination works, and iterate results without loading every page into memory. Ideal for large API datasets, sync jobs, and streaming responses efficiently.
Architecture fit. cerbero/lazy-json-pages is a thin read-side adapter: it turns a paginated JSON API into an Illuminate\Support\LazyCollection, fetching pages through concurrent HTTP requests (Guzzle promises in standalone mode, Http::pool inside Laravel). It belongs in the data-ingestion layer of an application — the place where you currently write while ($hasMore) { $page = $client->get(...); ... } loops. It is not a domain component, a queue, or a persistence layer, and it should not leak past an adapter boundary into business logic. For Laravel shops pulling large third-party datasets (CRM exports, marketplace catalogs, SaaS list endpoints, accounting APIs), it maps cleanly onto an existing service/job pattern.
Integration feasibility. The public surface is small: a request factory/URL builder, a page-parsing callable that returns items plus "is there a next page" metadata, and the resulting LazyCollection. A mid-level Laravel developer can wire up a first endpoint in a day. Composer install is clean; the package is genuinely small (low hundreds of lines of meaningful logic), which matters both for review effort and for the option to vendor it later.
Technical risk. Medium. The dangerous combination is concurrency + laziness + third-party pagination semantics, where failures are quiet rather than loud. Specific concerns:
Key questions. Which endpoints, and are they cursor- or offset-paginated? What is the realistic max page size and total item count per sync? What are the provider's documented rate limits and concurrency tolerance? Do we need resumability across job failures, and what is the checkpoint granularity? Will we use Laravel's Http client or raw Guzzle — and can we standardize on one? Who owns the fork if upstream goes quiet? What is the reconciliation check that proves we ingested every record?
Stack fit. Laravel 9–11 and PHP 8.1+, with Guzzle 7 underneath. Note the "framework-agnostic" framing is only half true: it depends on illuminate/support and illuminate/http even outside a full Laravel app. Treat it as a Laravel-adjacent dependency and evaluate it against your Laravel upgrade cadence, not as a neutral library.
Migration path. Do not sprinkle LazyCollection construction across the codebase. Sequence it as:
RemoteCatalogSource or similar) returning domain DTOs, not raw arrays.Compatibility. Pin the major version and verify Composer constraints against your PHP/Laravel matrix. The two transport paths (standalone Guzzle vs. Http::pool) behave differently around exception propagation and connection reuse — pick one and standardize so behavior is uniform across integrations. Watch Guzzle minor releases and PHP 8.x deprecations.
Sequencing. Week 1: spike one endpoint at concurrency 5, measure throughput and 429 rate. Week 2: adapter + DTO mapping + structured logging (page number, item count, latency, status). Week 3: retry/backoff policy, max-page guard, dead-letter handling for failed syncs. Week 4: shadow comparison and production rollout to a single integration. Only then expand to additional APIs.
Maintenance. Low churn, infrequent releases — cheap to upgrade, but also cheap for the maintainer to walk away. Recommended posture: pin the version and vendor the core (either into packages/ or via a Composer patch) so a yanked release or an abandoned upstream cannot break production. The code volume makes this a realistic afternoon's work, which is the strongest argument for adopting it at all.
Support. No commercial support; issue-tracker-only. Your team owns the runbook. On-call engineers need to understand two things that are not obvious from the outside: how a mid-stream page failure propagates out of a lazy collection, and what a saturated pool looks like in logs versus a rate-limited upstream.
Scaling. The bottleneck is almost always the upstream API's rate limit, not PHP. Concurrency is the single tuning knob: raise until 429s appear, then settle below that with jittered retries. Keep page sizes bounded and cap the pool so memory stays predictable. For multi-hour syncs, pair the lazy collection with periodic checkpointing so a failure at page 900 does not restart at page 1 — this is the difference between a tolerable and an intolerable operational story.
Failure modes. Mid-stream page failure aborts iteration and leaves partial state the consumer must handle; slow pages stall the pool and can cascade into job timeouts; a wrong pagination predicate yields silent truncation or an infinite loop (guard with a hard page ceiling); offset pagination over mutable data produces duplicates and skips (dedupe downstream); uncontrolled concurrency can get you rate-limited or banned by the provider.
Ramp-up. Under a day for an experienced Laravel developer — the conceptual lift is LazyCollection semantics and async pooling, not this package. Two caveats worth writing down internally: when not to use it (small datasets, strict ordering requirements, APIs with aggressive per-IP limits where a simple sequential loop is safer), and how to verify completeness after a sync. A short internal README covering concurrency defaults, the page-parser contract, and the reconciliation check will pay for itself on the first incident.
How can I help you explore Laravel packages today?