typescript 57 lines · 7 steps

Type-safe pagination queries in NestJS

A validated DTO turns raw query strings into safe pagination inputs and a structured paginated response.

Explained by highlit
1import { IsInt, IsOptional, Max, Min } from 'class-validator';
2import { Type } from 'class-transformer';
3 
4export class PaginationQueryDto {
5 @IsOptional()
6 @Type(() => Number)
7 @IsInt()
8 @Min(1)
9 page: number = 1;
10 
11 @IsOptional()
12 @Type(() => Number)
13 @IsInt()
14 @Min(1)
15 @Max(100)
16 limit: number = 20;
17 
18 get skip(): number {
19 return (this.page - 1) * this.limit;
20 }
21}
22 
23export interface PaginatedResult<T> {
24 data: T[];
25 meta: {
26 total: number;
27 page: number;
28 limit: number;
29 pageCount: number;
30 };
31}
32 
33@Controller('articles')
34export class ArticlesController {
35 constructor(private readonly articlesService: ArticlesService) {}
36 
37 @Get()
38 async findAll(
39 @Query() { page, limit, skip }: PaginationQueryDto,
40 ): Promise<PaginatedResult<Article>> {
41 const [data, total] = await this.articlesRepository.findAndCount({
42 order: { publishedAt: 'DESC' },
43 skip,
44 take: limit,
45 });
46 
47 return {
48 data,
49 meta: {
50 total,
51 page,
52 limit,
53 pageCount: Math.ceil(total / limit),
54 },
55 };
56 }
57}
01 / 01
STEP 01

Walkthrough

Space play step click any line
Three takeaways
  1. 1Decorator-based DTOs let you validate and coerce query parameters declaratively instead of writing manual parsing logic.
  2. 2A computed getter on the DTO keeps derived values like the SQL offset next to the fields they depend on.
  3. 3Returning a generic result envelope gives every list endpoint a consistent, typed shape of data plus metadata.

Related explainers

Share this explainer

Here's the card — post it anywhere.

Type-safe pagination queries in NestJS — share card
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code