NestJS: How to create cursor-based pagination (2 examples)

Updated: February 28, 2024 By: Guest Contributor Post a comment

Cursor-based pagination is an efficient method for navigating through large datasets. Unlike traditional offset pagination, cursor-based pagination uses a pointer to fetch records after or before it, providing a faster and more scalable solution. In this tutorial, we’ll explore how to implement cursor-based pagination in NestJS with two practical examples.

Setup Your NestJS Project

First, ensure you have NestJS CLI installed. If not, you can install it with:

npm i -g @nestjs/cli

Then, create a new NestJS project:

nest new pagination-example

After the project setup, navigate to your project directory:

cd pagination-example

Example 1: Basic Cursor-Based Pagination

In our first example, we’ll implement a simple cursor-based pagination feature for a fictional “posts” resource. Let’s start by creating a Post module, controller, and service.

nest generate module posts
nest generate controller posts
nest generate service posts

In the posts.service.ts, we’ll add a method to simulate fetching posts from a database:

findAll(cursor: string, limit: number): Post[] { /* Simulation of fetching posts with cursor and limit */ }

In posts.controller.ts, we’ll create a GET endpoint to handle pagination:

@Get()
findAll(@Query('cursor') cursor: string, @Query('limit') limit: number): Post[] { return this.postsService.findAll(cursor, limit); }

With this basic setup, clients can fetch posts by providing a cursor ID and a limit. The cursor ID should be the ID of the last post in their current list.

Example 2: Advanced Cursor-Based Pagination with Real Database

In the second example, let’s integrate a real database. First, install TypeORM and the respective driver of your database, for example for PostgreSQL:

npm install @nestjs/typeorm typeorm pg

Next, configure the TypeORM module in app.module.ts:

imports: [TypeOrmModule.forRoot({...})]

In our post.entity.ts, define a Post entity:

@Entity()
export class Post {
@PrimaryGeneratedColumn()
id: number;

@Column()
title: string;

@CreateDateColumn()
createdAt: Date;
}

Modify posts.service.ts to use the repository for accessing posts:

constructor(@InjectRepository(Post) private postRepository: Repository) {}

async findAll(cursor: string, limit: number): Promise<Post[]> { return this.postRepository.find({ where: { id: MoreThan(cursor) }, order: { id: 'ASC' }, take: limit }); }

This setup ensures that whenever a client requests posts with a cursor, the service fetches posts from the database with IDs greater than the cursor ID, complying with cursor-based pagination principles.

Implementing Cursor Encoding and Decoding

To enhance our cursor pagination, let’s implement cursor encoding and decoding techniques. This practice obfuscates the cursor’s true value, adding a layer of security and complexity.

Install the js-base64 library for easy encoding and decoding:

npm install js-base64

In your service, you can now encode the ID:

const encodedCursor = Base64.encode(cursor.toString());

And decode it when a request is received:

const decodedCursor = Base64.decode(cursor);

This method allows for a more secure way to handle cursors, protecting against potential data inference or manipulation.

Conclusion

Cursor-based pagination provides a swift, scalable way to manage large datasets in NestJS applications. By following the examples provided, developers can implement both basic and advanced cursor pagination systems. With cursor encoding and real-database integration, your pagination can achieve enterprise-level efficiency and security.