How to Handle Pagination in Strapi v4 with SvelteKit
Tags: svelte, strapi, tutorial
Published: 2022-05-09 12:06:41

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](https://strapi.io/) for the backend and implement the pagination controls UI by building the frontend in [Svelte](https://svelte.dev).

Strapi is a headless CMS (content management system) for [Node.js](https://nodejs.org/en/) 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](https://kit.svelte.dev/), 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](https://github.com/ravgeetdhillon/strapi-svelte-pagination).

## 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](https://nodejs.org/en/download/):** this tutorial uses Node v14.19.0.
- **[Strapi](https://strapi.io/getting-started):** this tutorial uses Strapi v4.1.0.
- **[SvelteKit](https://kit.svelte.dev/):** 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:

```bash
     mkdir strapi-svelte-pagination
```

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:

```bash
     npx create-strapi-app@latest backend --quickstart
```

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](http://localhost:1337/admin/auth/register-admin) in your browser. At this point, set up your administrative user:

![Strapi admin registration](https://i.imgur.com/05j9qAV.png)

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

![Strapi Dashboard](https://i.imgur.com/tIUXksI.png)

### 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](https://i.imgur.com/n8FMGqv.png)

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

![Creating the **Article** content type](https://i.imgur.com/QLN475h.png)

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](https://i.imgur.com/WlzjG3J.png)

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

![Creating the **description** field in the **Article** content type](https://i.imgur.com/vmWA6HN.png)

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](https://i.imgur.com/J7axA02.png)

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](https://www.npmjs.com/@faker-js/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:

```bash
    npm install --save-dev @faker-js/faker
```

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:

```js
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 };
```

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:

```js
"use strict";

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

module.exports = {
  ...
  // 2
  async bootstrap() {
    await seedArticlesCollection();
  },
};
```

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:

```bash
   npm run develop
```

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

![Strapi console messages](https://i.imgur.com/ATbZywK.png)

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

![**Article** content type with newly created records](https://i.imgur.com/Pn7s4qe.png)

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](https://www.postman.com/) and then send a GET request to the Article API endpoint: [localhost:1337/api/articles](http://localhost:1337/api/articles). You won’t be able to access the endpoint until you allow public access to it.

![Forbidden GET request](https://i.imgur.com/LABVEZr.png)

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](https://i.imgur.com/Kslra2m.png)

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](https://i.imgur.com/xs4SGSo.png)

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

![List of articles from Strapi API](https://i.imgur.com/uKeO2NY.png)

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

![Pagination details in API response](https://i.imgur.com/hRfDr15.png)

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](https://i.imgur.com/nwmRS73.png)

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](https://i.imgur.com/rX70LPw.png)

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:

```bash
  npm init svelte@next frontend
```

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

![Svelte project setup](https://i.imgur.com/37SHLDN.png)

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:

```bash
  cd frontend
  npm install
```

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

```bash
  npm run dev -- --open
```

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

![Svelte home page](https://i.imgur.com/KffDorz.png)

### Installing NPM Packages

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

* **[axios](https://www.npmjs.com/package/axios):** this lets you work with HTTP requests.
* **[qs](https://www.npmjs.com/package/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:

```bash
  npm install axios qs
```

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:

```svelte
<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>
```

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](http://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:

```bash
  npm run dev
```

Visit [localhost:3000](http://localhost:3000) and see your articles fetched from Strapi in the UI:

![Articles page in Svelte](https://i.imgur.com/5QvQdKx.png)

### 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:

```svelte
<!-- ... -->
<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>
<!-- ... -->
```

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`:

```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>
```

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](http://localhost:3000) to check out the pagination controls on the top and bottom of the index page:

![Page size control in Svelte](https://i.imgur.com/82jx5vi.png)

![Pagination in Svelte](https://i.imgur.com/wTAwcBV.png)

### Testing Pagination

On the [localhost:3000](http://localhost:3000), make sure that the pagination functionality works as expected by playing with the pagination controls:

![738Xazv.gif](https://d2zv2ciw0ln4h1.cloudfront.net/uploads/738_Xazv_3161590dd9.gif)


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

## Conclusion

In this tutorial, you learned to work with [Strapi](https://strapi.io/) 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](https://github.com/ravgeetdhillon/strapi-svelte-pagination).