How to Implement Pagination with JavaScript

Pagination is the process of dividing a large set of data into smaller individual pages, making the information easier to process and digest when delivered to the user. In this tutorial, we are going to demonstrate how to implement a JavaScript pagination system in three different ways.

Why you need JavaScript pagination

Creating a pagination system has several benefits. Imagine you have a blog with thousands of articles. It would be impossible to list all of them on one page. Instead, you could create a pagination system where the user can navigate to different pages.

Pagination also reduces server load, as only a segment of the data needs to be transferred every time a request is made. This enhances your application's overall performance, delivers a better user experience, and, as a result, improves the website's SEO.

To get started, we have prepared a demo project here:

🔗 Download demo project

How to implement JavaScript pagination - the easy way

When you think about dividing items into pages, what is the easiest logic that comes to mind?

For example, you could retrieve all articles from the database as a single array and then split them into smaller arrays based on a certain page size using the splice() method.

JavaScript Pagination

index.js

javascript
import express from "express";
import { PrismaClient } from "@prisma/client";
 
const app = express();
const port = 3001;
 
const prisma = new PrismaClient();
 
app.set("views", "./views");
app.set("view engine", "pug");
 
app.use(express.urlencoded({ extended: true })); // for parsing application/x-www-form-urlencoded
app.use(express.json());
 
app.use("/statics", express.static("statics"));
 
// The easy way
// ===========================================================
app.get("/pages/:page", async function (req, res) {
  const pageSize = 5;
  const page = Number(req.params.page);
  const posts = await prisma.post.findMany({});
 
  const pages = [];
  while (posts.length) {
    pages.push(posts.splice(0, pageSize));
  }
 
  const prev = page === 1 ? undefined : page - 1;
  const next = page === pages.length ? undefined : page + 1;
 
  res.render("list", {
    posts: pages[page - 1],
    prev: prev,
    next: next,
  });
});
 
app.listen(port, () => {
  console.log(
    `Blog application listening on port ${port}. Visit http://localhost:${port}.`
  );
});

In this example, the page size is set to 5, meaning there will be five posts on every page.

Line 21, page is the current page number.

Line 22, posts is an array of all posts stored in the database.

Line 24 to 27, we split the array based on the page size. The splice(index, count) method takes two parameters, index and count. It splices and returns count number of elements from the array, starting from index. The remaining part of the array will be assigned to posts.

JavaScript array splice

Line 29 and 30 each point to the previous and next page based on the current page number.

javascript
const prev = page === 1 ? undefined : page - 1;
const next = page === pages.length ? undefined : page + 1;

If the current page is 1, prev will equal undefined because there is no previous page in this case. Otherwise, it equals page - 1.

next, on the other hand, will equal to undefined if the current page equals pages.length, meaning the current page is the last one. Otherwise it equals page + 1.

And lastly, the posts for the current page (pages[page - 1]), along with prev and next, will be sent to the corresponding view (list.pug).

list.pug

pug
ul
    each post in posts
        li
            a(href="#") #{post.title}
    else
        li No post found.
 
if prev
    a(href=`/pages/${prev}`) Prev
 
if next
    a(href=`/pages/${next}`) Next

As you probably have realized, this solution has one problem. You have to retrieve all the posts from the database before splitting them into individual pages. This is a huge waste of resources, and in practice, it will likely take a very long time for the server to process this amount of data.

How to implement offset-based pagination in JavaScript

So we need a better strategy. Instead of retrieving all the posts, we can first determine an offset based on the page size and the current page number. This way, we can skip these posts and only retrieve the ones we want.

In our example, the offset equals pageSize * (page - 1), and we are going to retrieve the pageSize number of posts after this offset.

offset based pagination in JavaScript

The following example demonstrates how this can be done using Prisma. The skip specifies the offset, and take defines the number of posts to retrieve after that offset.

javascript
// Offset pagination
// ===========================================================
app.get("/pages/:page", async function (req, res) {
  const pageSize = 5;
  const page = Number(req.params.page);
  const posts = await prisma.post.findMany({
    skip: pageSize * (page - 1),
    take: pageSize,
  });
 
  const prev = page === 1 ? undefined : page - 1;
  const next = page + 1;
 
  res.render("list", {
    posts: posts,
    prev: prev,
    next: next,
  });
});

The frontend remains the same in this case.

list.pug

pug
ul
    each post in posts
        li
            a(href="#") #{post.title}
    else
        li No post found.
 
if prev
    a(href=`/pages/${prev}`) Prev
 
if next
    a(href=`/pages/${next}`) Next

Of course, other ORM frameworks can achieve the same result, but the logic remains the same. At the end of this tutorial, we will provide some resources to help you create JavaScript pagination systems using other ORM frameworks.

How to implement infinite scroll in JavaScript

Besides the offset-based pagination, there is a popular alternative called cursor-based pagination. This strategy is often used to create infinite scroll or the Load More button.

As the name suggests, the cursor-based pagination requires a cursor. When the user first visits a list of posts, the cursor points to the last item in the array.

Javascript pagination cursor based strategy initial state

When the user clicks on the Load More button, a request is sent to the backend, which returns the next batch of posts. The frontend takes the transferred data and programmatically renders the new posts, and the corresponding cursor is updated to point to the last item of this new batch of posts.

Javascript pagination cursor based next batch

When it comes to actually implementing this cursor-based pagination, things get a bit more complicated, as this strategy requires the frontend and the backend to work together. But don’t worry, we’ll go through this step by step.

First of all, let’s create the root route (/). When the user visits this page, the first ten posts will be retrieved, and the cursor will point to the id of the last post. Recall that at(-1) retrieves the last element of the array.

javascript
//Cursor-based pagination (load more)
// ===========================================================
const pageSize = 10;
 
app.get("/", async function (req, res) {
  const posts = await prisma.post.findMany({
    take: pageSize,
  });
  const last = posts.at(-1);
  const cursor = last.id;
 
  res.render("list", {
    posts: posts,
    cursor: cursor,
  });
});

Notice that the cursor will be transferred to the frontend as well. This is very important, and you must make sure that the cursor on both ends is always in sync.

list.pug

pug
button(id="loadMore" data-cursor=`${cursor}`) Load More
 
ul(id="postList")
    each post in posts
        li
            a(href="#") #{post.title}
    else
        li No post found.
 
script(src="/statics/js/app.js")

The initial value of the cursor will be saved in the attribute data-cursor of the Load More button, which can then be accessed by JavaScript in the frontend. In this example, we put all the frontend JavaScript code inside /statics/js/app.js.

/statics/js/app.js

javascript
document.addEventListener("DOMContentLoaded", function () {
  const loadMoreButton = document.getElementById("loadMore");
  const postList = document.getElementById("postList");
 
  let cursor = loadMoreButton.getAttribute("data-cursor");
 
  loadMoreButton.addEventListener("click", function () {
    fetch("/load", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        cursor: cursor,
      }),
    })
      . . .
  });
});

When the Load More button is clicked, a POST request will be sent to /load to retrieve the next batch of posts. Again, notice that you need to send the cursor back to the server, making sure they are always in sync.

Next, create a route handler for /load. This route handler takes the cursor and retrieves the next ten posts from the database. Remember to skip one so the post that cursor is pointing at will not be duplicated.

Javascript pagination cursor based next batch

javascript
app.post("/load", async function (req, res) {
  const { cursor } = req.body;
 
  const posts = await prisma.post.findMany({
    take: pageSize,
    skip: 1,
    cursor: {
      id: Number(cursor),
    },
  });
 
  const last = posts.at(-1);
  const newCursor = last.id;
 
  res.status(200).json({
    posts: posts,
    cursor: newCursor,
  });
});

This handler will send a 200OK response back to the frontend, along with the retrieved posts, which will again be picked up by the frontend JavaScript code.

/statics/js/app.js

javascript
document.addEventListener("DOMContentLoaded", function () {
  const loadMoreButton = document.getElementById("loadMore");
  const postList = document.getElementById("postList");
 
  let cursor = loadMoreButton.getAttribute("data-cursor");
 
  loadMoreButton.addEventListener("click", function () {
    fetch("/load", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        cursor: cursor,
      }),
    })
      .then((response) => response.json())
      .then((data) => {
        if (data.posts && data.posts.length > 0) {
          data.posts.forEach((post) => {
            const li = document.createElement("li");
            const a = document.createElement("a");
 
            a.href = "#";
            a.textContent = post.title;
 
            li.appendChild(a);
            postList.appendChild(li);
          });
          cursor = data.cursor;
        } else {
          loadMoreButton.textContent = "No more posts";
          loadMoreButton.disabled = true;
        }
      })
      .catch((error) => {
        console.error("Error loading posts:", error);
      });
  });
});

Conclusion

Both the offset and cursor strategies have their pros and cons. For example, the offset strategy is the only option if you want to jump to any specific page.

However, this strategy does not scale at the database level. If you want to skip the first 1000 items and take the first 10, the database must traverse the first 1000 records before returning the ten requested items.

The cursor strategy is much easier to scale because the database can directly access the pointed item and return the next 10. However, you cannot jump to a specific page using a cursor.

Lastly, before we wrap up this tutorial, here are some resources you might find helpful if you are creating pagination systems with a different ORM framework.

  • Sequelize Limits and Pagination
  • TypeORM Pagination
  • Objection.js
  • Mikro ORM Pagination

Happy coding!

Learn, Build & Launch

With Our Full Stack Dev Roadmap with Next.js Boilerplate

Start for FREE 🎉

Subscribe below to grab our free Full-Stack Web Developer Starter Kit 👇

Also follow us onorwhere we share coding tips daily.

Course Outline
Introduction+
  • 1.Course Introduction
HTML & CSS+
  • 1.Preparations
  • 2.HTML Elements
  • 3.Text Elements
  • 4.Layout Elements
  • 5.Cascading Style Sheet
  • 6.Basic Selectors
  • 7.Advanced Selectors
  • 8.Colors
  • 9.Loading Font
  • 10.Font Customization
  • 11.Text Customization
  • 12.Text Spacing
  • 13.Text Alignment
  • 14.Functions
  • 15.@ Rules
  • 16.Links
  • 17.Lists
  • 18.Tables
  • 19.Forms
  • 20.Form Fields
  • 21.Media Files
  • 22.Aspect Ratio
  • 23.Object Fit
  • 24.Cursor Behavior
  • 25.Scroll Behavior
  • 26.Visibility & Opacity
  • 27.Box Shadow
  • 28.Color Background
  • 29.Image Background
  • 30.Background Attachment
  • 31.Gradient
  • 32.Blend Modes
  • 33.The Box Model
  • 34.Border
  • 35.Padding & Margin
  • 36.Element Size
  • 37.Display Types
  • 38.Overflow
  • 39.Float
  • 40.Position
  • 41.Z index
  • 42.The Column Layout
  • 43.The Grid Layout
  • 44.Grid Flow
  • 45.Grid Gap
  • 46.Grid Spanning
  • 47.Grid Alignment
  • 48.The Flexbox Layout
  • 49.Flex Gap
  • 50.Flex Order
  • 51.Basis, Grow & Shrink
  • 52.Flexbox Alignment
  • 53.Calculator
  • 54.Filters
  • 55.Transforms
  • 56.Transition
  • 57.Transition Timing Functions
  • 58.Transition Delay
  • 59.Animations
  • 60.Animation Iteration Count
  • 61.Animation Direction
  • 62.Animation Fill Mode
  • 63.Responsive Design
  • 64.Viewport
  • 65.Media Query
  • 66.Responsive Media
  • 67.Responsive Text
  • 68.Responsive Layout (Flexbox)
  • 69.Responsive Layout (Grid)
  • 70.Responsive Layout (Legacy)
  • 71.Layout Without Media Query
  • 72.Recreating YouTube
  • 73.Building the Layout
  • 74.Building the Navbar
  • 75.Building the Sidebar
  • 76.Building the Main Section
JavaScript Fundamentals+
  • 1.Introduction
  • 2.Basic Syntax
  • 3.Variables
  • 4.Data Types
  • 5.Numbers & BigInt
  • 6.Strings
  • 7.Boolean Values
  • 8.Undefined & Null
  • 9.Type Conversion
  • 10.If Statements
  • 11.Switch Statements
  • 12.While Loops
  • 13.For Loops
  • 14.Introducing Functions
  • 15.Variable Scope
  • 16.Arrays
  • 17.Mutating Array
  • 18.Searching Array
  • 19.Sorting Array
  • 20.Looping Array
  • 21.Objects
  • 22.Looping Object
  • 23.JSON
  • 24.Symbols
  • 25.Maps
  • 26.Sets
  • 27.Define Functions
  • 28.Function Arguments
  • 29.Rest Parameter & Spread Syntax
  • 30.Error Handling
  • 31.Pure Functions
  • 32.Functions as Value
  • 33.Higher Order Functions
  • 34.Function Factory
  • 35.Function Currying
  • 36.Function Wrapper
  • 37.Recursion
  • 38.Closure
  • 39.Methods
  • 40.Investigating "this"
  • 41.Losing "this"
  • 42.Constructor Functions
  • 43.Getters & Setters
  • 44.Prototypes Introduction
  • 45.Creating Prototypes
  • 46.Inspecting Prototypes
  • 47.Constructor with Prototype
  • 48.The Class Notation
  • 49.Class Inheritance
  • 50.Static Properties
  • 51.Private Properties
  • 52.Object Oriented Programming
  • 53.A Banking App
  • 54.Working with Date
  • 55.The Math Object
  • 56.JavaScript Modules
  • 57.Throwing Errors
  • 58.Asynchronous Programming
  • 59.Promise
  • 60.Resolve Promise
  • 61.Promise Chaining
  • 62.Async & Wait
  • 63.Best Practices
JavaScript in the Frontend+
  • 1.The DOM Tree
  • 2.Selecting Elements in the DOM
  • 3.DOM Navigation
  • 4.Changing Elements
  • 5.Adding Elements
  • 6.Removing Elements
  • 7.Event Handling
  • 8.Event Propagation
  • 9.Some Common Events
  • 10.Image Slider: Creating HTML
  • 11.Image Slider: Adding Styles
  • 12.Image Slider: Adding Scripts
  • 13.Image Slider That "Slides"
  • 14.Regular Expressions
  • 15.Regex Flags
  • 16.Regex Matching Character Sets
  • 17.Regex Boundary
  • 18.Regex Groups & Quantifiers
  • 19.Regex Lookahead & Lookbehind
  • 20.Regex Related Methods
  • 21.Calculator
  • 22.Canvas
JavaScript in the Backend+
  • 1.Setting Up a Dev Environment
  • 2.Network & HTTP
  • 3.A Basic Web App
  • 4.Express.js
  • 5.Routing
  • 6.MVC Architecture
  • 7.The Model Layer
  • 8.CRUD Operations
  • 9.The Controller Layer
  • 10.The View Layer
  • 11.Blog
  • 12.ORM Integration
  • 13.Creating a Post
  • 14.Uploading Files
  • 15.Showing a Post
  • 16.Updating a Post
  • 17.Deleting a Post
  • 18.Database Relations
  • 19.CRUD Tags
  • 20.Adding & Removing Tags
  • 21.Showing Tags & Posts
  • 22.Middleware
  • 23.Browser Data Storage
  • 24.User Registration
  • 25.User Authentication
  • 26.User Authorization
  • 27.User Management
  • 28.Going to Production
React.js+
  • 1.Introducing React.js
  • 2.Setting Up React Project
  • 3.React Project Structure
  • 4.JSX
  • 5.Conditional Rendering
  • 6.List Rendering
  • 7.Adding Styles
  • 8.Components
  • 9.Import & Export Component
  • 10.Variables in Components
  • 11.Props
  • 12."children" Prop
  • 13.Event Handlers
  • 14.Event Handlers & Props
  • 15.Event Propagation
  • 16.Event Prevent Default
  • 17.Introducing State
  • 18.States are Local
  • 19.Object States
  • 20.Multiple States
  • 21.Shared States
  • 22.Form Handling
  • 23.Component Lifecycle
  • 24.Built-In Components
  • 25.Todo List
  • 26.Add New Task
  • 27.Remove Task
  • 28.Edit Task
  • 29.Complete a Task
  • 30.View Pending Tasks
  • 31.Organize State with Reducer
  • 32.The Reducer Function
  • 33.Dispatch Actions
  • 34.Passing Data with Context
  • 35.Context Provider
  • 36.Access Context
  • 37.Use Context with State
  • 38.Manipulate DOM with Refs
  • 39.Side Effects
  • 40.Side Effect Dependencies
  • 41.Side Effect Clean Up
  • 42.Weather App
  • 43.Get Weather by Coordinate
  • 44.Get Weather by City
  • 45.Display Weather by User Location
  • 46.Creating Custom Hooks
  • 47.Other React Hooks
  • 48.Rules of React
  • 49.Building a Full-Stack Blog App
  • 50.Project Setup
  • 51.React Router
  • 52.List Posts
  • 53.Create a New Post
  • 54.Show a Single Post
  • 55.Update a Post
  • 56.Delete a Post
  • 57.Implement Dark Theme
Full-Stack with Next.js+
  • 1.Next.js Basics
  • 2.Routing
  • 3.Pages & Layout
  • 4.Server vs. Client Components
  • 5.API Routes
  • 6.Server Actions
  • 7.Database Integration
  • 8.Data Fetching
  • 9.Error Handling
  • 10.Middleware
  • 11.Links, Navigation & Redirection
  • 12.Images
  • 13.Scripts
  • 14.Fonts
  • 15.Lazy Loading
  • 16.Caching
  • 17.Loading UI
  • 18.SaaS Platform
  • 19.User Authentication
  • 20.Magic Link
  • 21.Custom Emails
  • 22.Protecting Routes
  • 23.OAuth Providers
  • 24.Dashboard
  • 25.Role Based Access Control
  • 26.Payment Integration
  • 27.Pricing Page
  • 28.Stripe Checkout
  • 29.Payment Webhook
AI for Full-Stack Developers+
  • 1.What Are LLMs?
  • 2.Setting Up OpenAI Account
  • 3.Tokens
  • 4.Context Window
  • 5.Compare LLM Models
  • 6.OpenAI Model Playground
  • 7.Building a Chatbot
  • 8.Send Message to OpenAI
  • 9.Talking to GPT-4
  • 10.Talking to GPT-5
  • 11.Response Streaming
  • 12.Sending Data
  • 13.Receiving Data
  • 14.Streaming with Server Sent Events
  • 15.Chatbot Streaming Backend
  • 16.Chatbot Streaming Frontend
  • 17.Prompt Engineering
  • 18.System Prompt
  • 19.Role Switcher
  • 20.Prompting Techniques
  • 21.Structured Output
  • 22.Get Current Weather
  • 23.Tools & Functions
  • 24.Structured Output vs. Tool Calling
  • 25.What is an Agent
  • 26.What is RAG
  • 27.Chunking & Embedding
  • 28.Building a RAG Agent
  • 29.Setting Up the Database
  • 30.Document Processing
  • 31.Split Text into Chunks
  • 32.Generate Embeddings
  • 33.Listing Processed Documents
  • 34.Chat with RAG Agent
  • 35.Semantic Search
  • 36.Generate a Response
Recent Community Articles
  • The Ultimate Guide to Becoming an AI Engineer
  • How to Learn Full-Stack Web Development in 2025 (With Projects)
  • Top Website Optimization Methods in 2025
  • How to Build Interactive Forms Using HTML and CSS
  • How to Create a Modern App with Django and Vue
  • How to Send HTTP Requests Using JavaScript
  • Vue.js Fundamentals
  • How to Reverse a String in JavaScript
  • How to Reverse an Array in JavaScript
  • JavaScript Fundamentals 2025
  • Why you need JavaScript pagination
  • How to implement JavaScript pagination - the easy way
  • How to implement offset-based pagination in JavaScript
  • How to implement infinite scroll in JavaScript
  • Conclusion

Full Stack AI Dev

RoadmapCommunityPricing

SaaS Boilerplate

Next.js BoilerplateDocumentation

Contribute

Write for UsStyle Guide

Free Tools

HTML CompilerJavaScript CompilerNode.js CompilerReact CompilerNext.js Compiler

Legals

Privacy PolicyTerms of ServiceContact Us
© 2026TheDevSpace.io| All Rights Reserved
RoadmapSaaS BoilerplatePricingCommunity
CheatsheetsHTML CompilerJavaScript CompilerNode CompilerReact CompilerNext.js Compiler
Get Started
CourseNext.js BoilerplatePricingCommunity
Freebies
CheatsheetsHTML CompilerJavaScript CompilerNode CompilerReact CompilerNext.js Compiler