Hey there, devs! 👋 If you've ever struggled with paginating large datasets efficiently, you're in the right place. Today, we'll implement cursor-based pagination in a NestJS API using TypeORM. This approach is far superior to offset-based pagination when dealing with large databases. Let's dive in! 🏊♂️
What We'll Cover 🔥
- Using a
createdAt
cursor to fetch records efficiently. - Implementing a paginated endpoint in NestJS.
- Returning data with a cursor for the next page.
1️⃣ Creating a DTO for Pagination Parameters
First, let's define a DTO to handle pagination parameters:
import { IsOptional, IsString, IsNumber } from 'class-validator';
import { Transform } from 'class-transformer';
export class CursorPaginationDto {
@IsOptional()
@IsString()
cursor?: string; // Receives the `createdAt` of the last item on the previous page
@IsOptional()
@Transform(({ value }) => parseInt(value, 10))
@IsNumber()
limit?: number = 10; // Number of items per page (default: 10)
}
2️⃣ Implementing the Query in the Service
Now, let's create the logic in our service:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { CursorPaginationDto } from './dto/cursor-pagination.dto';
@Injectable()
export class UserService {
constructor(
@InjectRepository(User)
private readonly userRepository: Repository<User>,
) {}
async getUsers(cursorPaginationDto: CursorPaginationDto) {
const { cursor, limit } = cursorPaginationDto;
const queryBuilder = this.userRepository
.createQueryBuilder('user')
.orderBy('user.createdAt', 'DESC')
.limit(limit + 1); // Fetching one extra record to check if there's a next page
if (cursor) {
queryBuilder.where('user.createdAt < :cursor', { cursor });
}
const users = await queryBuilder.getMany();
const hasNextPage = users.length > limit;
if (hasNextPage) {
users.pop(); // Remove the extra item
}
const nextCursor = hasNextPage ? users[users.length - 1].createdAt : null;
return {
data: users,
nextCursor,
};
}
}
3️⃣ Creating the Controller
Finally, let's expose our paginated endpoint:
import { Controller, Get, Query } from '@nestjs/common';
import { UserService } from './user.service';
import { CursorPaginationDto } from './dto/cursor-pagination.dto';
@Controller('users')
export class UserController {
constructor(private readonly userService: UserService) {}
@Get()
async getUsers(@Query() cursorPaginationDto: CursorPaginationDto) {
return this.userService.getUsers(cursorPaginationDto);
}
}
4️⃣ Defining the Database Model
Here's our User
entity:
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn('uuid')
id: string;
@Column()
name: string;
@CreateDateColumn()
createdAt: Date;
}
How Cursor-Based Pagination Works ⚡
1️⃣ The first request to GET /users
does not include a cursor. It fetches the first limit
records.
2️⃣ The backend returns a nextCursor
, which is the createdAt
timestamp of the last user in the response.
3️⃣ To fetch the next page, the frontend makes a request to GET /users?cursor=2024-03-09T12:34:56.000Z
, and the backend will return users created before that timestamp.
4️⃣ This process continues until nextCursor
is null
, meaning there are no more records left.
Example JSON Response 📝
{
"data": [
{ "id": "1", "name": "John", "createdAt": "2024-03-09T12:00:00.000Z" },
{ "id": "2", "name": "Anna", "createdAt": "2024-03-09T11:45:00.000Z" }
],
"nextCursor": "2024-03-09T11:45:00.000Z"
}
Why Use Cursor-Based Pagination? 🤔
✅ Better Performance: Avoids OFFSET
, which slows down large datasets.
✅ Scalability: Works seamlessly with millions of records.
✅ Optimized Queries: Using indexed fields like createdAt
makes queries lightning-fast. ⚡
Conclusion 🎯
Cursor-based pagination is a game-changer for handling large datasets in APIs. 🚀 It's faster, more efficient, and ensures a smoother experience for your users. Now you’re ready to implement it in your own NestJS project! 💪
Got questions or improvements? Drop them in the comments! 💬 Happy coding! 😃