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! π
Top comments (0)