Laravel Tutorial · Database and Eloquent · Lesson 19 of 27
Pagination
Split long lists into pages with paginate(), simplePaginate() and cursorPaginate(), and return JSON with page links for APIs.
Paginating queries
Call paginate(15) instead of get() and Laravel reads ?page= from the request, runs a count query plus a limited query, and returns a paginator:
- In Blade,
{{ $posts->links() }}renders page links. - Returned from an API route, it becomes JSON with
data,current_page,last_page,totalandlinks.
Variants:
| Method | Best for |
|---|---|
paginate() |
Page numbers and totals (runs a COUNT query) |
simplePaginate() |
Just "next/previous" (no COUNT, cheaper) |
cursorPaginate() |
Infinite scroll and big tables: stable, no OFFSET, no duplicates when rows are added |
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Route;
use Illuminate\Support\Facades\Schema;
Schema::create('products', function (Blueprint $t) { $t->id(); $t->string('name'); });
class Product extends Model
{
public $timestamps = false;
protected $guarded = [];
}
foreach (range(1, 7) as $i) {
Product::create(['name' => "Product $i"]);
}
Route::get('/products', function () {
$page = Product::orderBy('id')->paginate(3);
return [
'items' => $page->pluck('name'),
'page' => $page->currentPage(),
'last_page' => $page->lastPage(),
'total' => $page->total(),
'next' => $page->nextPageUrl(),
];
});
Route::get('/feed', fn () => Product::select('id', 'name')->orderBy('id')->cursorPaginate(3)); // the cursor encodes the last id
visit('GET', '/products');
visit('GET', '/products?page=3');
$feed = json_decode(visit('GET', '/feed')->getContent(), true);
$next = parse_url($feed['next_page_url'], PHP_URL_QUERY);
visit('GET', '/feed?'.$next); // follow the cursor to the next page
Output
GET /products -> 200 {"items":["Product 1","Product 2","Product 3"],"page":1,"last_page":3,"total":7,"next":"http:\/\/localhost\/products?page=2"}
GET /products?page=3 -> 200 {"items":["Product 7"],"page":3,"last_page":3,"total":7,"next":null}
GET /feed -> 200 {"data":[{"id":1,"name":"Product 1"},{"id":2,"name":"Product 2"},{"id":3,"name":"Product 3"}],"path":"http:\/\/localhost\/feed","per_page":3,"next_cursor":"eyJpZCI6MywiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","next_page_url":"http:\/\/localhost\/feed?cursor=eyJpZCI6MywiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","prev_cursor":null,"prev_page_url":null}
GET /feed?cursor=eyJpZCI6MywiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ -> 200 {"data":[{"id":4,"name":"Product 4"},{"id":5,"name":"Product 5"},{"id":6,"name":"Product 6"}],"path":"http:\/\/localhost\/feed","per_page":3,"next_cursor":"eyJpZCI6NiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","next_page_url":"http:\/\/localhost\/feed?cursor=eyJpZCI6NiwiX3BvaW50c1RvTmV4dEl0ZW1zIjp0cnVlfQ","prev_cursor":"eyJpZCI6NCwiX3BvaW50c1RvTmV4dEl0ZW1zIjpmYWxzZX0","prev_page_url":"http:\/\/localhost\/feed?cursor=eyJpZCI6NCwiX3BvaW50c1RvTmV4dEl0ZW1zIjpmYWxzZX0"}Run this on your computer — see “Get Started” for setup.
Test yourself
Which pagination method avoids OFFSET and suits infinite scrolling on big tables?
paginate()simplePaginate()cursorPaginate()chunk()
Show answer
Answer: cursorPaginate()