php 46 lines · 8 steps

Building a cursor-paginated feed in Laravel

A JSON API endpoint that validates input, builds an efficient query, and returns cursor-paginated posts with metadata.

Explained by highlit
1<?php
2 
3namespace App\Http\Controllers\Api;
4 
5use App\Http\Controllers\Controller;
6use App\Http\Resources\PostResource;
7use App\Models\Post;
8use Illuminate\Http\Request;
9 
10class FeedController extends Controller
11{
12 public function index(Request $request)
13 {
14 $validated = $request->validate([
15 'cursor' => ['nullable', 'string'],
16 'per_page' => ['nullable', 'integer', 'min:1', 'max:50'],
17 'tag' => ['nullable', 'string', 'exists:tags,slug'],
18 ]);
19 
20 $perPage = $validated['per_page'] ?? 15;
21 
22 $posts = Post::query()
23 ->select(['id', 'user_id', 'title', 'slug', 'excerpt', 'published_at'])
24 ->with('author:id,name,avatar_url')
25 ->withCount('comments')
26 ->whereNotNull('published_at')
27 ->where('published_at', '<=', now())
28 ->when($validated['tag'] ?? null, fn ($query, $tag) => $query->whereHas(
29 'tags',
30 fn ($q) => $q->where('slug', $tag)
31 ))
32 ->orderByDesc('published_at')
33 ->orderByDesc('id')
34 ->cursorPaginate($perPage)
35 ->withQueryString();
36 
37 return PostResource::collection($posts)->additional([
38 'meta' => [
39 'next_cursor' => $posts->nextCursor()?->encode(),
40 'prev_cursor' => $posts->previousCursor()?->encode(),
41 'has_more' => $posts->hasMorePages(),
42 'per_page' => $posts->perPage(),
43 ],
44 ]);
45 }
46}
01 / 01
STEP 01

Walkthrough

Space play ←→ step click any line
Three takeaways
  1. 1Cursor pagination scales better than offset pagination for large, ordered feeds because it avoids expensive OFFSET scans.
  2. 2Selecting explicit columns and eager-loading relations keeps a feed query lean and dodges N+1 problems.
  3. 3API Resources plus additional() cleanly separate serialized records from pagination metadata in the response.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Building a cursor-paginated feed in Laravel — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code