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
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Cursor pagination scales better than offset pagination for large, ordered feeds because it avoids expensive OFFSET scans.
- 2Selecting explicit columns and eager-loading relations keeps a feed query lean and dodges N+1 problems.
- 3API Resources plus additional() cleanly separate serialized records from pagination metadata in the response.
Related explainers
php
<?php class NameParser {
Parsing a full name into components in PHP
string-parsing
arrays
normalization
Intermediate
8 steps
php
<?php namespace App\Services\Checkout;
Validating coupons with Laravel's Pipeline
pipeline
chain of responsibility
transactions
Intermediate
7 steps
php
<?php namespace App\Services;
How a password strength validator works in PHP
validation
regular-expressions
data-driven
Intermediate
8 steps
php
<?php namespace App\Services;
Building a cached daily leaderboard in Laravel
caching
aggregation
eager-loading
Intermediate
9 steps
rust
use chrono::{Duration, NaiveDate}; #[derive(Debug)] pub struct DateRange {
Parsing and iterating date ranges in Rust
error-handling
iterators
parsing
Intermediate
7 steps
php
<?php final class RotatingFileLogger {
How a rotating file logger works in PHP
logging
file-rotation
io
Intermediate
9 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-cursor-paginated-feed-in-laravel-explained-php-4e09/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.