1001Ferramentas
πŸ“ƒ Generators

Fake Paginated JSON Generator

Generates JSON with fake pagination structure (page, pageSize, total, items) to mock a REST API.

β€”

Mock paginated JSON: API design from offset/limit to cursors

Pagination is the most common ergonomic problem in REST and GraphQL API design, and it is also one of the easiest to get wrong. A mock paginated JSON generator lets front-end teams, mobile developers and SDK authors build screens, infinite-scroll feeds and tables before the backend is ready, with response envelopes that match the production contract. It also doubles as a teaching artefact: a single tool that emits offset/limit, cursor, keyset and timestamp variants makes the trade-offs concrete and visible side by side.

The four canonical patterns are easy to recognise. Offset/limit uses ?page=2&limit=20 or ?offset=20&limit=20; simple, jumpable, but unstable when rows are inserted between requests and slow on large tables because the database still has to count and skip the first OFFSET rows. Cursor-based pagination uses an opaque token like ?cursor=eyJpZCI6MTAwfQ==; stable across inserts, fast even on billions of rows, but you cannot jump to "page 50". Keyset (a.k.a. seek) exposes the sort key directly β€” ?after_id=100&limit=20 β€” and is what most cursors decode to internally. Timestamp-based, ?since=2024-01-01T00:00:00Z, is the natural fit for activity feeds and webhooks.

Response envelopes and standards

A paginated response is almost always wrapped in an envelope with two parts: a data array of items and a meta object describing the page. Common fields are current_page, per_page, total, total_pages, next_url and prev_url. The JSON:API specification standardises this with a links object containing self, first, last, prev and next URLs β€” a HATEOAS-style design where the client follows links instead of constructing them. GraphQL Relay uses connection + edges + pageInfo with endCursor and hasNextPage; Stripe returns has_more and an SDK-side auto_paging_iter(); GitHub ships pagination data in an HTTP Link header instead of the body.

{
  "data": [{ "id": 21, "name": "Item 21" }],
  "meta": {
    "current_page": 2,
    "per_page": 20,
    "total": 137,
    "total_pages": 7
  },
  "links": {
    "first": "/items?page=1&limit=20",
    "prev":  "/items?page=1&limit=20",
    "next":  "/items?page=3&limit=20",
    "last":  "/items?page=7&limit=20"
  }
}

Cursor pagination in depth

Opaque cursors are usually base64-encoded JSON containing the sort key plus the id of the last item returned, so the server can issue a WHERE (sort_key, id) > (last_sort, last_id) query that hits an index. Opacity matters: if clients learn the format they will tamper with it, and changing the schema later breaks every cached cursor in the wild. Always include a version byte. Cursors are the right default for infinite feeds, activity logs, transactions and any endpoint backed by a table that grows continuously; offset/limit is fine for small admin tables and search results where "page 12" is meaningful.

Page sizes, limits and edge cases

Sensible defaults: default page size 25, max 100, reject anything larger with HTTP 400. Always document the cap. Test the edge cases your front-end will hit: an empty result, a result of exactly limit items (off-by-one in "has next page" logic), the last page, a request beyond the last page (return empty data, not 404), and concurrent inserts (cursor stays stable, offset shifts). A mock generator that lets you set total, per_page and current_page independently is the fastest way to cover all four in component tests.

Frequently asked questions

Cursor or offset? Cursor for infinite feeds, large tables, append-only data and anything performance-sensitive. Offset for small admin lists, search results and any case where the user benefits from jumping to a specific page.

What page size should I use? Default 25, allow 1 to 100, reject anything bigger. Mobile clients usually want 20; data exports want 100.

Which envelope is the standard? There is no single winner. JSON:API is the most prescriptive; Stripe-style (data + has_more) is the most copied by SaaS APIs; GitHub-style Link headers are the cleanest but the least discoverable for SDK consumers.

Do I need a total count? Not always. Returning total requires a second query (SELECT COUNT(*)) that can dominate the request time. For infinite feeds, has_more is enough. For paginated tables that show "1-20 of 137", the count is unavoidable.

Related Tools