Laravel API Resources, Cursor Pagination &amp; Rate Limiting | Mohamed Said        [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com) [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles  ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

       [  ](https://github.com/EG-Mohamed)       

 [ Home ](https://www.msaied.com) [ Projects ](https://www.msaied.com/projects) [ Articles ](https://www.msaied.com/articles) [ Certificates ](https://www.msaied.com/certificates) [ Contact ](https://www.msaied.com#contact-section) 

  [ home ](https://www.msaied.com)    [ articles ](https://www.msaied.com/articles)    Laravel API Resources: Sparse Fieldsets, Cursor Pagination, and Per-Route Rate Limiting        On this page       1. [  Beyond Basic JsonResource ](#beyond-basic-jsonresource)
2. [  Sparse Fieldsets Without a Package ](#sparse-fieldsets-without-a-package)
3. [  Cursor Pagination for Large Datasets ](#cursor-pagination-for-large-datasets)
4. [  What the Query Actually Looks Like ](#what-the-query-actually-looks-like)
5. [  Per-Route Rate Limiting with Named Limiters ](#per-route-rate-limiting-with-named-limiters)
6. [  Surfacing Limit Headers ](#surfacing-limit-headers)
7. [  Key Takeaways ](#key-takeaways)

  ![Laravel API Resources: Sparse Fieldsets, Cursor Pagination, and Per-Route Rate Limiting](https://cdn.msaied.com/606/93349b03f4527b9100157c6774bb4ce2.png)

  #laravel   #api   #eloquent   #rate-limiting  

 Laravel API Resources: Sparse Fieldsets, Cursor Pagination, and Per-Route Rate Limiting 
=========================================================================================

     29 Aug 2026      4 min read    ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said  

       Table of contents

1. [  01   Beyond Basic JsonResource  ](#beyond-basic-jsonresource)
2. [  02   Sparse Fieldsets Without a Package  ](#sparse-fieldsets-without-a-package)
3. [  03   Cursor Pagination for Large Datasets  ](#cursor-pagination-for-large-datasets)
4. [  04   What the Query Actually Looks Like  ](#what-the-query-actually-looks-like)
5. [  05   Per-Route Rate Limiting with Named Limiters  ](#per-route-rate-limiting-with-named-limiters)
6. [  06   Surfacing Limit Headers  ](#surfacing-limit-headers)
7. [  07   Key Takeaways  ](#key-takeaways)

 Beyond Basic JsonResource
-------------------------

Most Laravel APIs start with a thin `JsonResource` wrapper and a `paginate()` call. That works until your payloads balloon, your cursors drift, and a single client hammers one endpoint. This article tackles three concrete improvements you can ship today.

---

Sparse Fieldsets Without a Package
----------------------------------

JSON:API defines sparse fieldsets (`?fields[resource]=id,name,email`). You can implement a lightweight version directly in a base resource.

```php
// app/Http/Resources/SparseResource.php
abstract class SparseResource extends JsonResource
{
    protected function sparse(array $fields): array
    {
        $requested = collect(
            explode(',', request()->query('fields', ''))
        )->filter()->values();

        if ($requested->isEmpty()) {
            return $fields;
        }

        return array_intersect_key($fields, array_flip($requested->all()));
    }
}

```

```php
// app/Http/Resources/UserResource.php
class UserResource extends SparseResource
{
    public function toArray(Request $request): array
    {
        return $this->sparse([
            'id'         => $this->id,
            'name'       => $this->name,
            'email'      => $this->email,
            'created_at' => $this->created_at->toISOString(),
        ]);
    }
}

```

A request to `GET /users?fields=id,name` now returns only those two keys. No extra package, no reflection magic — just an `array_intersect_key` on the resolved field map.

> **Tip:** Validate allowed fields in a Form Request to prevent leaking internal column names.

---

Cursor Pagination for Large Datasets
------------------------------------

`paginate()` uses `OFFSET`, which forces the database to scan all preceding rows. On a table with millions of records that becomes expensive fast. `cursorPaginate()` uses a keyset derived from the last seen row.

```php
// routes/api.php
Route::get('/events', function (Request $request) {
    return EventResource::collection(
        Event::query()
            ->orderBy('id')
            ->cursorPaginate(50)
    );
});

```

The response includes `next_cursor` and `prev_cursor` tokens. Clients pass `?cursor=` on subsequent requests.

### What the Query Actually Looks Like

With `orderBy('id')` and a cursor pointing at id `1000`, Laravel generates:

```sql
SELECT * FROM events WHERE id > 1000 ORDER BY id ASC LIMIT 51;

```

That `51` is intentional — Laravel fetches one extra row to determine whether a next page exists, then discards it. The query hits the primary key index regardless of table size.

**Caveats:**

- Cursor pagination requires a stable, unique sort column (or composite).
- You cannot jump to an arbitrary page — it is forward/backward only.
- Use `CursorPaginator::currentCursorName()` if you need a custom query-string key.

---

Per-Route Rate Limiting with Named Limiters
-------------------------------------------

The global `throttle:60,1` middleware is too blunt for a real API. Define named limiters in `AppServiceProvider` (or a dedicated `RateLimitServiceProvider`).

```php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    RateLimiter::for('exports', function (Request $request) {
        return $request->user()
            ? Limit::perHour(10)->by($request->user()->id)
            : Limit::perHour(2)->by($request->ip());
    });

    RateLimiter::for('search', function (Request $request) {
        return [
            Limit::perMinute(30)->by($request->user()?->id ?? $request->ip()),
            Limit::perDay(5000)->by($request->user()?->id ?? $request->ip()),
        ];
    });
}

```

Attach them per route:

```php
Route::get('/reports/export', ExportController::class)
    ->middleware('throttle:exports');

Route::get('/search', SearchController::class)
    ->middleware('throttle:search');

```

Returning an **array** of `Limit` objects enforces multiple windows simultaneously — a burst guard (per-minute) and a daily budget in one declaration.

### Surfacing Limit Headers

Laravel automatically adds `X-RateLimit-Limit`, `X-RateLimit-Remaining`, and `Retry-After` headers when the limiter fires. Clients can back off gracefully without guessing.

---

Key Takeaways
-------------

- **Sparse fieldsets** reduce payload size with a single `array_intersect_key` — no package required.
- **`cursorPaginate()`** replaces `OFFSET` with a keyset query; always pair it with an indexed sort column.
- **Named rate limiters** let you apply different burst and daily budgets per endpoint, scoped to authenticated users or IP addresses.
- Returning an array of `Limit` objects from a limiter enforces multiple time windows at once.
- Laravel's built-in rate-limit headers give clients everything they need to implement polite retry logic.

 Found this useful?

          [  ](https://twitter.com/intent/tweet?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Flaravel-api-resources-sparse-fieldsets-cursor-pagination-and-per-route-rate-limiting&text=Laravel+API+Resources%3A+Sparse+Fieldsets%2C+Cursor+Pagination%2C+and+Per-Route+Rate+Limiting) [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fwww.msaied.com%2Farticles%2Flaravel-api-resources-sparse-fieldsets-cursor-pagination-and-per-route-rate-limiting) 

 Frequently Asked Questions 
----------------------------

  3 questions  

     Q01  When should I prefer cursorPaginate() over paginate() in Laravel?        Use cursorPaginate() whenever you are paginating large tables (hundreds of thousands of rows or more) and do not need random page access. It avoids the OFFSET scan by using a keyset derived from the last seen row, which keeps query time constant regardless of how deep into the dataset you are. 

      Q02  Can I apply multiple rate limits to a single route in Laravel?        Yes. Return an array of Limit objects from your named limiter closure. Laravel evaluates each limit independently, so you can enforce a per-minute burst cap and a per-day total cap simultaneously on the same route. 

      Q03  Is the sparse fieldsets approach safe? Could clients request internal columns?        The SparseResource pattern is safe because you define the allowed field map explicitly in toArray(). Clients can only request keys that already exist in that map — they cannot access raw database columns or relationships you have not exposed. 

  Continue reading

 More Articles 
---------------

 [ View all    ](https://www.msaied.com/articles) 

 [ ![Recursive CTEs and Hierarchical Data in Laravel with PostgreSQL](https://cdn.msaied.com/607/ac508dd27011f0f2c57b0bee7707b740.png) laravel postgresql eloquent 

### Recursive CTEs and Hierarchical Data in Laravel with PostgreSQL

Learn how to query trees and hierarchies—categories, org charts, threaded comments—using recursive CTEs in Pos...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 29 Aug 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/recursive-ctes-and-hierarchical-data-in-laravel-with-postgresql) [ ![Laravel Starter Kits Now Ship with Vite+](https://cdn.msaied.com/604/ff70a112664fcb8b68719ab94a842145.png) Laravel Vite+ Starter Kits 

### Laravel Starter Kits Now Ship with Vite+

All Laravel starter kits now use Vite+, the unified toolchain that replaces ESLint and Prettier with Oxlint an...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 28 Aug 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-starter-kits-now-ship-with-vite) [ ![Laravel Queues at Scale: Backpressure, Dead-Letter Queues, and Graceful Degradation](https://cdn.msaied.com/602/fcffaaa5442f84486d6059eaa4106d26.png) laravel queues reliability 

### Laravel Queues at Scale: Backpressure, Dead-Letter Queues, and Graceful Degradation

Beyond basic queue workers: learn how to implement backpressure signals, dead-letter queues, and graceful degr...

  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MJ.jpg)  Mohamed Said 

 28 Aug 2026     3 min read  

  Read    

 ](https://www.msaied.com/articles/laravel-queues-at-scale-backpressure-dead-letter-queues-and-graceful-degradation) 

   [  ![Mohamed Said](https://cdn.msaied.com/01KT78WE565VEMM3PSNQAAB0MH.png)   Mohamed Said Laravel Backend Engineer  ](https://www.msaied.com)Senior Backend Engineer specializing in Laravel, scalable SaaS platforms, APIs, and cloud infrastructure. I build secure, high-performance web applications that help businesses grow.

Explore

- [Home](https://www.msaied.com)
- [Projects](https://www.msaied.com/projects)
- [Articles](https://www.msaied.com/articles)
- [Certificates](https://www.msaied.com/certificates)
- [Contact](https://www.msaied.com#contact-section)

Connect

- [   hello@msaied.com ](mailto:hello@msaied.com)
- [   +20 109 461 9204 ](tel:+201094619204)

© 2026 Mohamed Said. All rights reserved.

 [  ](https://github.com/EG-Mohamed) [  ](https://www.linkedin.com/in/msaiedm/) [  ](https://wa.me/201094619204) [  ](mailto:hello@msaied.com) [  ](https://drive.google.com/file/u/0/d/1MF20IPRJyzfy32mhEutjL5EpSls0w2Q8/view)
