Build1 publisher2 min readPublished
A per-API config block absorbs the pagination differences behind one paginate() call
The author of laravel-rest pointed one Eloquent-style client at 62 public APIs across 470 unmocked scenarios and published the wire logs for three of them. Where the rows and the page total live is declared per API.
The Engineer · Build desk

What happened
- The author of laravel-rest built the package so Eloquent-style calls such as Post::where(...)->paginate() would run against external HTTP APIs, replacing a hand-rolled client and pagination loop per service.
- He then assembled a catalog of public APIs picked for their structure: page, offset and cursor pagination, bare arrays and envelope shapes, JSON:API, OData, Socrata, GraphQL and JSON-RPC.
- The package was pointed at 62 of those APIs across 470 scenarios, with real requests and no mocks, on a nightly run.
- The write-up publishes request and response output for three of the APIs and does not state how many of the 470 scenarios passed.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- cost The uniform call site is paid for in per-service mapping. Someone has to write the model class, the data key and the field names for page size, total and next link before the shared code works on a new API.
- capability Because the return values are Illuminate's own paginator and collection, remote API rows drop into a Blade view or an API resource without an adapter layer in between.
- exposure A nightly suite with no mocks puts 62 third parties' outages and rate limits into the maintainer's test results, so a red run has to be triaged before it says anything about the package.
`Pokemon::paginate(3)` is one GET. The model declares the endpoint, `$dataKey = 'results'` for where the rows sit, and `$primaryKey = 'name'`, because PokeAPI addresses records by name and not by an integer id [6]. The client config declares where the counting metadata sits: `'pagination' => ['style' => 'offset', 'total' => 'count', 'next' => 'next']` [7]. Back comes `Illuminate\Pagination\LengthAwarePaginator`, and the rows arrive in a collection that extends Illuminate's own [8]. `total()` is 1351 and `lastPage()` is 451 [9]. 1351 rows at three to a page is 450.33, so 451 is the ceiling [17].
The variation between APIs lives in those config keys. On the Art Institute of Chicago API the style is `page`, the total is at `pagination.total` and the next link at `pagination.next_url` [11]. crates.io calls the page size `per_page`, so the config maps `'limit' => 'per_page'` and reads the total from `meta.total` [14]. `total()` reports whatever the named path holds, so onboarding an API starts with finding that field in its envelope.
Envelope stripping is declared the same way. `toArray()` on artwork 27992 returns id, title and artist_id, and the `info` and `config` blocks the API wraps around the payload never reach the model [10].
The relation case is the one I would test hardest. `belongsTo(Agent::class, 'artist_id')` resolves to `GET agents/{artist_id}` [12], and fetching two artworks with `with('artist')` put three requests on the wire: the list, then `agents/40610` and `agents/40810` at once [13]. The artist ids are distinct, so lazy access would have issued the same three [19]. The gain is one round trip of latency, since the second artist fetch does not wait on the first. The author lists concurrent requests alongside eager loading and memoization as work he did before he built the catalog [16].
Sampling by pagination style and envelope shape is the right instinct for this test. The run averages about 7.6 scenarios per API [18], roughly a list call, a single record, a relation and a couple of failure modes each.
The author, who publishes on dev.to as sanchescom, wrote that every external API he had to talk to from Laravel ended up with a client of its own, a pagination loop of its own, and its own way of signalling that a record was not there [2]. He also wrote that he "started wondering whether the whole approach only looked uniform because I'd only tested it on APIs I'd chosen" [3]. On the evidence published, the claim that holds is narrower than uniformity across 62 APIs: a page total, a foreign key and an envelope were each expressible in config on PokeAPI, the Art Institute of Chicago and crates.io.
What to watch
- A per-scenario result table would show which of the 62 APIs needed non-default handling. That table is what would test the uniformity claim.
- Whether the cursor-paginated and JSON-RPC members of the catalog can fill a LengthAwarePaginator, which expects a total.
- Whether the nightly run separates upstream outages and rate limits from package regressions in its output.