How to Implement Laravel Eloquent Pagination in Inertia.js

I was recently working on an e-commerce platform project in laravel and ran into an issue where it was not obvious how to integrate laravel's eloquent pagignation function with inertiajs, so in this article, I'll go over how I solved this issue.

The issue

Imagine we had a Laravel + InertiaJs vue project with a post controller and page like so:

...
class PostController extends Controller
{
    public function index()
    {
        $posts = Post::all(5);

        return Inertia::render("Posts", [
            "posts" => $posts,
        ]);
    }
    ...
}
<script setup lang="ts">
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout.vue";
import { Head, Link } from "@inertiajs/vue3";

interface Post {
    id: number;
    title: string;
    description: string;
}

const { posts } = defineProps<{ posts: Post[] }>();
</script>

<template>
    <Head title="Posts" />

    <AuthenticatedLayout>
        <template #header>
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                Posts, count {{ posts.length }}
            </h2>
        </template>

        <div class="py-12">
            <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
                <div
                    v-for="post in posts"
                    :key="post.id"
                    class="bg-white overflow-hidden shadow-sm sm:rounded-lg"
                >
                    <div class="p-6 text-gray-900">{{ post.title }}</div>
                </div>

        </div>
    </AuthenticatedLayout>
</template>

In the above image, we have 50 posts that are displayed in a page - that's a lot of scrolling - we can simplify it by using Laravel's eloquent model query builder pagination method like so

...
class PostController extends Controller
{
    public function index()
    {
        // $posts = Post::all();
        $result = Post::paginate(15);

        return Inertia::render("Posts", [
            "result" => $result,
        ]);
    }
    ...
}

But now we have a problem in out frontend:

[Vue warn]: Invalid prop: type check failed for prop "posts". Expected Array, got Object

We can fix this by changing out frontend props to receive the Laravel pagination results which is of the form

{
   "total": 50,
   "per_page": 15,
   "current_page": 1,
   "last_page": 4,
   "first_page_url": "http://laravel.app?page=1",
   "last_page_url": "http://laravel.app?page=4",
   "next_page_url": "http://laravel.app?page=2",
   "prev_page_url": null,
   "path": "http://laravel.app",
   "from": 1,
   "to": 15,
   "data":[
        {
            // Record...
        },
        {
            // Record...
        }
   ]
}

So in our vue setup script we have:

<script setup lang="ts">
import AuthenticatedLayout from "@/Layouts/AuthenticatedLayout.vue";
import { Head, Link } from "@inertiajs/vue3";

interface Post {
    id: number;
    title: string;
    description: string;
}

interface PaginationResults {
   "total": number,
   "per_page": number,
   "current_page": number,
   "last_page": number,
   "first_page_url": string,
   "last_page_url": string,
   "next_page_url": string,
   "prev_page_url": string,
   "path": string,
   "from": number,
   "to": number,
   "data": Post[]
}

const { result } = defineProps<{ result: PaginationResults }>();
</script>

and update our template to utilize this:

<template>
    <Head title="Posts" />

    <AuthenticatedLayout>
        <template #header>
            <h2 class="font-semibold text-xl text-gray-800 leading-tight">
                Posts, page
                {{ result.current_page }}, count {{ result.per_page }}
            </h2>
        </template>

        <div class="py-12">
            <div class="max-w-7xl mx-auto sm:px-6 lg:px-8">
                <div
                    v-for="post in result.data"
                    :key="post.id"
                    class="bg-white overflow-hidden shadow-sm sm:rounded-lg"
                >
                    <div class="p-6 text-gray-900">{{ post.title }}</div>
                </div>

                <div
                    class="bg-white w-full flex justify-center items-center gap-8"
                >
                    <Link :href="result.prev_page_url" class="px-2">prev</Link>
                    <Link
                        v-for="page in result.last_page"
                        :href="`${result.path}?page=${page}`"
                        class="px-1"
                    >
                        {{ page }}
                    </Link>
                    <Link :href="result.next_page_url" class="px-2">next</Link>
                </div>
            </div>
        </div>
    </AuthenticatedLayout>
</template>

And our final result:

Much better, even though I could probably do better with the styling but this is not an article about tailwindcss XD.

Thanks to HNG Internship for inspiring me to make this blogpost. If you want to join me and thousands of others in improving your skills and networking with the best of the best checkout hng.tech/premium