How to Handle Pagination in Strapi v4 with SvelteKit

Shada - May 9 '22 - - Dev Community

If you use any kind of web or mobile application, you may have come across a data table that lets you view data by breaking it up into multiple pages. In the world of software development, this is known as pagination.

Pagination is an optimization technique that is used both on the frontend and backend to enhance the performance of your applications. With pagination, you can skip to the desired page and view the results for that particular page without loading any additional data. In this tutorial, you’ll learn how to work with Strapi for the backend and implement the pagination controls UI by building the frontend in Svelte.

Strapi is a headless CMS (content management system) for Node.js to build and develop APIs. It’s free and open-source, and lets you customize its behavior according to your needs.

To build the frontend, you’ll use SvelteKit, which is a framework for building web applications of all sizes. It gives you the best frontend and backend capabilities in a single-page application.

The entire source code for the tutorial is available in this GitHub repo.

What is Pagination?

As mentioned above, pagination is an optimization technique that is used to enhance the performance of your applications. For instance, if you have a huge blog that contains more than ten thousand articles, you’ll have an API that delivers that data to your frontend application.

If you try to fetch and send all the articles at once, you may run into bandwidth issues, latency issues, slow server response times, and memory-related issues. Even if you’re successful in sending all the articles, you may experience issues on the frontend, like a slow-response browser freeze, since you have to render tens of thousands of records on the frontend. In short, your browser can become unresponsive.

All these issues can lead to frustrated users and revenue loss.

The best way to overcome these issues is to fetch the articles in small quantities. For example, you can fetch the first fifty articles, and then once the user reaches the end of the list, you can fetch the next fifty articles, and so on. This keeps your server and client-side performant and provides a positive user experience. This whole process is pagination.

Pagination Types

Generally, there are two ways you can implement pagination:

Offset-Based Pagination

In offset-based pagination, the following two inputs are used to slice the data from the database:

  • Start is used to specify the index from where the records are fetched.
  • Limit is used to specify the number of records to fetch.

Cursor-Based Pagination

In cursor-based pagination, the limit and cursor inputs are used to slice the data from the database:

  • Limit is used to specify the number of records on each page.
  • Cursor is used to specify the number of records to skip before fetching the records and is used in combination with the limit input.

Prerequisites

To follow along with this tutorial, you’ll need the following installed:

  • Node.js: this tutorial uses Node v14.19.0.
  • Strapi: this tutorial uses Strapi v4.1.0.
  • SvelteKit: this tutorial uses the next version of SvelteKit.

Project Setup

To set up the project, you’ll need a master directory that holds the code for both the frontend (Svelte) and backend (Strapi). To begin, open up your terminal, navigate to a path of your choice, and create a project directory by running the following command:

     mkdir strapi-svelte-pagination
Enter fullscreen mode Exit fullscreen mode

In the strapi-svelte-pagination directory, you’ll install both Strapi and Svelte projects.

Strapi v4 Setup

In your terminal, execute the following command to create the Strapi project:

     npx create-strapi-app@latest backend --quickstart
Enter fullscreen mode Exit fullscreen mode

This command will create a Strapi project with quickstart settings in the backend directory.

Once the execution for this command is complete, your Strapi project will start on port 1337 and open up localhost:1337/admin/auth/register-admin in your browser. At this point, set up your administrative user:

Strapi admin registration

Enter your details and click the Let’s start button, and you’ll be taken to the Strapi Dashboard:

Strapi Dashboard

Creating the Article Collection Type

Under the Plugins header in the left sidebar, click the Content-Type Builder tab, and then click Create new collection type to create a new Strapi collection.

Create new content type in Strapi

In the modal that appears, create a new collection type with “Article” as Display Name; then click Continue:

Creating the **Article** content type

Next, create two fields for your collection type:

  1. Add a new text field named “title” and select the Short text type.

Adding the **title** field in the **Article** content type

  1. Create a new text field named “description” and select the Long text type.

Creating the **description** field in the **Article** content type

Once you’ve added all the fields, click the Finish button and select Save. It should now show you the title and description fields:

**Article** content type

At this point, your collection type is set up, and the next thing you need to do is add some data to it.

Seeding Strapi with Articles

To effectively test out pagination, you need to add lots of records. Manually adding these records can take a lot of time, so you can write a short script to seed the Article collection type.

To begin, you need to shut down the Strapi development server and install Faker, which lets you generate massive amounts of fake (but realistic) data for testing and development. To do so, run the following command in your terminal:

    npm install --save-dev @faker-js/faker
Enter fullscreen mode Exit fullscreen mode

Next, create a utils directory in the src directory. Then in the utils directory, create a seed.js file and add the following code to it:

const { faker } = require("@faker-js/faker");

// 1
async function seedArticlesCollection() {
  const numberOfRecordsToCreate = 207;

  // 2
  for (let i = 0; i < numberOfRecordsToCreate; i++) {
    // 3
    await strapi.api.article.services.article.create({
      data: {
        title: faker.lorem.words(),
        description: faker.lorem.sentence(),
      },
    });
  }

  console.log(`Added ${numberOfRecordsToCreate} records`);
}

module.exports = { seedArticlesCollection };
Enter fullscreen mode Exit fullscreen mode

In the above code, you define a function (seedArticlesCollection) to seed the Article collection type. You also run a for loop to create a numberOfRecordsToCreate number of records. Then you use the Strapi service to create a new article and generate the title (faker.lorem.words()) and description (faker.lorem.sentence()) using Faker.

Next, open src/index.js and add the following code to it:

"use strict";

// 1
const { seedArticlesCollection } = require("./utils/seed");

module.exports = {
  ...
  // 2
  async bootstrap() {
    await seedArticlesCollection();
  },
};
Enter fullscreen mode Exit fullscreen mode

Here, you import the seedArticlesCollection function, and you call the seedArticlesCollection function in the bootstrap method that runs before your Strapi application starts.

Finally, start the Strapi development server by running the following command:

   npm run develop
Enter fullscreen mode Exit fullscreen mode

Once the server starts, check the console for the following message:

Strapi console messages

Next, check the Article collection type, and you should see records created there:

**Article** content type with newly created records

Now you can remove the seedArticlesCollection function call in the bootstrap method; otherwise, every time you restart the Strapi server, new records will be created.

Setting Up Permissions for Strapi Article API

Now that you’ve successfully seeded Strapi, next, you need to set up the permissions to allow access to the Strapi API. At this point, you have enough data in your Strapi CMS to test the API.

Start by opening Postman and then send a GET request to the Article API endpoint: localhost:1337/api/articles. You won’t be able to access the endpoint until you allow public access to it.

Forbidden GET request

To configure the permissions for your Article content type API endpoints, click on the Settings tab under the General header, and then select Roles under the Users & Permissions Plugin. Because you want to allow public access to your articles, you need to configure the permissions related to the Public role. Click the Edit pen icon on the right of the Public role:

Permissions and roles in Strapi

Scroll down to find the Permissions tab and check the find and findOne routes for the Article collection type. Once done, click on Save to save the updated permissions:

Articles API permissions for Public role

Navigate back to Postman and send a GET request to the localhost:1337/api/articles, and you’ll get a list of articles from Strapi:

List of articles from Strapi API

When you scroll down in the response window, you’ll find some meta details related to pagination:

Pagination details in API response

Next, send another request to the articles endpoint with pagination[page] query parameter to fetch the list of articles on page 2:

Paginated API response with page number

Then send another request to the articles endpoint with pagination[page] and pagination[pageSize] query parameters to fetch the list of articles on page 2 and a total of fifty items, respectively:

Paginated API response with page size and page number

And that’s it. Your Strapi project is ready, and you can connect it with any kind of frontend application.

Svelte Setup

In this section, you’ll build the Svelte frontend application and connect it to the Strapi backend.

Since your current terminal window is serving the Strapi project, open another terminal window and execute the following command to create a Svelte project:

  npm init svelte@next frontend
Enter fullscreen mode Exit fullscreen mode

On the terminal, you’ll be asked some questions about your Svelte project. For this tutorial, choose the options highlighted below:

Svelte project setup

Once you’ve answered all the questions, Svelte CLI will install some initial dependencies. Once this installation process is complete, navigate into the frontend directory and install all the remaining dependencies by running the following commands:

  cd frontend
  npm install
Enter fullscreen mode Exit fullscreen mode

When the installation is finished, run the following command to start the Svelte development server:

  npm run dev -- --open
Enter fullscreen mode Exit fullscreen mode

This will start the development server on port 3000 and take you to localhost:3000. Your first view of the Svelte website will look like this:

Svelte home page

Installing NPM Packages

For your Svelte application, you need the following two NPM packages:

  • axios: this lets you work with HTTP requests.
  • qs: this lets you parse and stringify the query strings with some added security.

Begin by shutting down the Svelte development server by pressing Control-C in your terminal; then execute the following command to install the above NPM packages:

  npm install axios qs
Enter fullscreen mode Exit fullscreen mode

Now, you’re ready to write code in your Svelte application to fetch articles.

Fetching Articles from Strapi

After you’ve set up the necessary packages for developing your Svelte website, you need to design an Article page. On this page, you’ll fetch the articles from the Strapi CMS and display them in the UI.

To begin, in the src/routes directory, open up index.svelte and replace the existing code with the following code:

<script>
// 1
    import { onMount } from 'svelte';
    import axios from 'axios';
    import * as qs from 'qs';

    // 2
    let stateLoading = true;
    let stateArticles = null;
    let stateMeta = null;
    let stateCurrentPageNumber = 1;
    let stateCurrentPageSize = 20;

    // 3
    async function getArticles(pageNumber, pageSize) {
        const query = qs.stringify(
            {
                pagination: {
                    page: pageNumber,
                    pageSize: pageSize
                }
            },
            {
                encodeValuesOnly: true
            }
        );
        const res = await axios.get(`http://localhost:1337/api/articles?${query}`);
        return {
            articles: res.data.data,
            meta: res.data.meta
        };
    }

    // 4
    async function updateArticlesByPage(pageNumber) {
            stateLoading = true;
            stateCurrentPageNumber = pageNumber;
        const { articles, meta } = await getArticles(stateCurrentPageNumber, stateCurrentPageSize);
        stateArticles = articles;
        stateMeta = meta;
        stateLoading = false;
    }

    // 5
    onMount(async () => {
        await updateArticlesByPage(stateCurrentPageNumber);
    });
</script>

<!-- 6 -->
<section>
    <div class="container">
        {#if stateLoading}
            <p>Loading...</p>
        {:else}
            <div>
                <h1>Strapi Articles ({stateMeta.pagination.total})</h1>

                <div class="mb-4">
                    <!-- 7 -->
                    {#each stateArticles as article}
                        <div class="mb-4">
                            <h2 class="h4">{article.id} - {article.attributes.title}</h2>
                            <p class="mb-1">{article.attributes.description}</p>
                        </div>
                    {/each}
                </div>
            </div>
        {/if}
    </div>
</section>
Enter fullscreen mode Exit fullscreen mode

In the code above, you import the required NPM packages and define the state variables.
You also define the getArticles function, to which you pass the page and pageSize parameters. Then you convert those parameters to a string using the stringify method from qs. Next, it makes a GET request to the /api/articles endpoint on localhost:1337 and returns an object containing a list of articles and pagination details in the meta.

After that, you define the updateArticlesByPage function to which you pass the pageNumber parameter. This function calls the getArticles function and updates the state variables: stateArticles and stateMeta. Then you call the udpateArticlesByPage function in the onMount method, which runs when the Svelte page or component is rendered for the first time and defines the HTML template for the index page. Finally, you loop over the fetched articles and render a list of articles on the current page.

Now, save your progress and restart the Svelte development server by running the following command in your terminal:

  npm run dev
Enter fullscreen mode Exit fullscreen mode

Visit localhost:3000 and see your articles fetched from Strapi in the UI:

Articles page in Svelte

Implementing Pagination Controls in Svelte

To implement the pagination controls in Svelte, update the HTML template in index.svelte by adding the following code under the else block:

<!-- ... -->
<h1>Strapi Articles ({stateMeta.pagination.total})</h1>

<div class="controls">
    <div>
        <label for="inputPageSize">Page Size</label>
        <!-- 1 -->
        <input
            name="inputPageSize"
            type="number"
            bind:value={stateCurrentPageSize}
            min="1"
            max={stateMeta.pagination.total}
        />
        <!-- 2 -->
        <button
            on:click|preventDefault={() => updateArticlesByPage(stateCurrentPageNumber)}
            disabled={stateMeta.pagination.total <= stateCurrentPageSize}>Apply</button
        >
    </div>
</div>

<!-- ... -->

<div class="controls">
    <!-- 3 -->
    <button
        on:click|preventDefault={() => updateArticlesByPage(--stateCurrentPageNumber)}
        disabled={stateMeta.pagination.page === 1}>Previous</button
    >
    <!-- 4 -->
    <div class="pagination">
        {#each { length: stateMeta.pagination.pageCount } as _, p}
            <button
                on:click|preventDefault={() => updateArticlesByPage(p + 1)}
                disabled={stateMeta.pagination.page === p + 1}>{p + 1}</button
            >
        {/each}
    </div>
    <!-- 5 -->
    <button
        on:click|preventDefault={() => updateArticlesByPage(++stateCurrentPageNumber)}
        disabled={stateMeta.pagination.page === stateMeta.pagination.pageCount}>Next</button
    >
</div>
<!-- ... -->
Enter fullscreen mode Exit fullscreen mode

In the code above, you define a numeric input field for specifying the page size and bind it to the stateCurrentPageSize state variable. You also define a button with a click handler (on:click) to call the updateArticlesByPage function.

Next, you define a Previous button that takes the user back by one page (--stateCurrentPageNumber). You disable this button if the user is currently on the first page, and you loop over the number of pages (stateMeta.pagination.pageCount) and create a button that takes the user to the exact page. Finally, you define a Next button that takes the user forward by one page (++stateCurrentPageNumber) and disable this button if the user is currently on the last page.

Next, as a bonus, add the following styles at the end of index.svelte:

<style>
    * {
        --light: #e2e2e2;
        --light-dark: #a0a0a0;
        --dark: #292929;
        --disabled: #c4c3c3;
        box-sizing: border-box;
        font-family: 'Inter';
        color: var(--dark);
    }
    section {
        max-width: 100%;
        padding-top: 1rem;
        padding-bottom: 1rem;
    }
    .container {
        max-width: 576px;
        width: 100%;
        margin: auto auto;
    }
    h1 {
        font-size: 2rem;
        margin-top: 0;
        margin-bottom: 1.25rem;
    }
    h2 {
        font-size: 1.5rem;
        margin-top: 0;
        margin-bottom: 0.5rem;
    }
    p {
        margin-top: 0;
    }
    label {
        font-size: 0.8rem;
    }
    .mb-4 {
        margin-bottom: 2rem;
    }
    .controls {
        display: flex;
        justify-content: space-between;
        align-items: center;
        border-top: 0.0625rem solid var(--light);
        border-bottom: 0.0625rem solid var(--light);
        padding-top: 1rem;
        padding-bottom: 1rem;
        margin-bottom: 2rem;
    }
    .pagination {
        display: flex;
        flex-wrap: nowrap;
        overflow: auto hidden;
        border-radius: 0.125rem;
        margin: auto 1rem;
    }
    .pagination::-webkit-scrollbar {
        height: 0;
    }
    .pagination button {
        border-radius: 0;
    }
    input {
        vertical-align: middle;
        outline: none;
        border: 0.0625rem solid var(--light);
        border-radius: 0.125rem;
        padding: 0.25rem 0.5rem;
    }
    input:focus {
        border: 0.0625rem solid var(--light-dark);
    }
    button {
        vertical-align: middle;
        cursor: pointer;
        background: var(--dark);
        outline: none;
        border: 0.0625rem solid var(--dark);
        color: #ffffff;
        border-radius: 0.125rem;
        padding: 0.25rem 0.5rem;
    }
    button:hover {
        background: #000000;
    }
    button:disabled {
        background: var(--disabled);
        border: 0.0625rem solid var(--disabled);
        cursor: not-allowed;
    }
</style>
Enter fullscreen mode Exit fullscreen mode

Because you can style the app based on your liking, the code above lets you use the existing styles.

Finally, save your progress and visit localhost:3000 to check out the pagination controls on the top and bottom of the index page:

Page size control in Svelte

Pagination in Svelte

Testing Pagination

On the localhost:3000, make sure that the pagination functionality works as expected by playing with the pagination controls:

738Xazv.gif

Now, you’ve successfully completed the pagination functionality in Svelte and Strapi CMS.

Conclusion

In this tutorial, you learned to work with Strapi pagination and implemented the pagination controls UI by building the frontend in Svelte. You can add pagination to API endpoints that return a huge amount of data and create better, more performant applications.

Pagination may seem like a complex topic, but once you go through implementing it, you can use the underlying techniques with different combinations of frontend and backend frameworks.

The entire source code for this tutorial is available in this GitHub repository.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .