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