๐ŸŽ‰ berenickt ๋ธ”๋กœ๊ทธ์— ์˜จ ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. ๐ŸŽ‰
Back
NestJs
21-Pagination-Page Pagination

1. DTO์— Page ํ”„๋กœํผํ‹ฐ ์ถ”๊ฐ€

paginatePosts()๋ฅผ 2๊ฐ€์ง€ ํ•จ์ˆ˜๋กœ ๋‚˜๋ˆŒ ๊ฒƒ์ด๋‹ค.

  • pagePaginatePosts
  • cursorPaginatePosts
posts.service.ts
1
// posts.service.ts ์ƒ๋žต
2
/***
3
* 1) ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜๋Š” pagination๋งŒ ๊ตฌํ˜„ํ•œ๋‹ค
4
*/
5
async paginatePosts(dto: PaginatePostDto) {
6
if (dto.page) {
7
return this.pagePaginatePosts(dto)
8
} else {
9
return this.cursorPaginatePosts(dto)
10
}
11
}
12
13
/*** ํŽ˜์ด์ง€ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜
14
* data: Data[],
15
* total: number
16
*/
17
async pagePaginatePosts(dto: PaginatePostDto) {}
18
19
/*** ์ปค์„œ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜
20
*
21
*/
22
async cursorPaginatePosts(dto: PaginatePostDto) {
23
const where: FindOptionsWhere<PostsModel> = {}
24
25
if (dto.where__id_less_than) {
26
where.id = LessThan(dto.where__id_less_than)
27
} else if (dto.where__id_more_than) {
28
where.id = MoreThan(dto.where__id_more_than)
29
}
30
31
const posts = await this.postsRepository.find({
32
where,
33
order: {
34
createdAt: dto.order__createAt,
35
},
36
take: dto.take,
37
})
38
39
/****
40
* ํ•ด๋‹น๋˜๋Š” ํฌ์ŠคํŠธ๊ฐ€ 0๊ฐœ ์ด์ƒ์ด๋ฉด, ๋งˆ์ง€๋ง‰ ํฌ์ŠคํŠธ๋ฅผ ๊ฐ€์ ธ์˜ค๊ณ 
41
* ์•„๋‹ˆ๋ฉด null์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.
42
*/
43
const lastItem = posts.length > 0 && posts.length === dto.take ? posts[posts.length - 1] : null
44
const nexttUrl = lastItem && new URL(`${PROTOCOL}://${HOST}/posts`)
45
46
/**** dto์˜ ํ‚ค๊ฐ’๋“ค์„ ๋ฃจํ•‘ํ•˜๋ฉด์„œ
47
* ํ‚ค๊ฐ’์— ํ•ด๋‹น๋˜๋Š” ๋ฒจ๋ฅ˜๊ฐ€ ์กด์žฌํ•˜๋ฉด, parame์— ๊ทธ๋Œ€๋กœ ๋ถ™์—ฌ๋„ฃ๋Š”๋‹ค.
48
* ๋‹จ, where__id_more_than ๊ฐ’๋งŒ lastItem์˜ ๋งˆ์ง€๋ง‰ ๊ฐ’์œผ๋กœ ๋„ฃ์–ด์ค€๋‹ค.
49
*/
50
if (nexttUrl) {
51
for (const key of Object.keys(dto)) {
52
if (dto[key]) {
53
if (key !== 'where__id_more_than' && key !== 'where__id_less_than') {
54
nexttUrl.searchParams.append(key, dto[key])
55
}
56
}
57
}
58
let key = null
59
if (dto.order__createAt === 'ASC') {
60
key = 'where__id_more_than'
61
} else {
62
key = 'where__id_less_than'
63
}
64
nexttUrl.searchParams.append(key, lastItem.id.toString())
65
}
66
67
/*** Response
68
* data : Data[],
69
* cursor : {
70
* after: ๋งˆ์ง€๋ง‰ Data์˜ ID
71
* }
72
* count: ์‘๋‹ตํ•œ ๋ฐ์ดํ„ฐ์˜ ๊ฐœ์ˆ˜
73
* next: ๋‹ค์Œ ์š”์ฒญ์„ ํ•  ๋–„ ์‚ฌ์šฉํ•  URL
74
*/
75
return {
76
data: posts,
77
cursor: {
78
after: lastItem?.id ?? null,
79
},
80
count: posts.length,
81
nest: nexttUrl?.toString() ?? null,
82
}
83
}

๊ทธ๋ฆฌ๊ณ  paginate-post.dto์— page ํ”„๋กœํผํ‹ฐ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ ํ•œ๋‹ค.

paginate-post.dto.ts
1
// ์ƒ๋žต
2
export class PaginatePostDto {
3
@IsNumber()
4
@IsOptional()
5
page?: number
6
7
// ์ƒ๋žต
8
}

2. Post ์‘๋‹ต ์ƒ์„ฑ

posts.service.ts
1
// posts.service.ts ์ƒ๋žต
2
/*** ํŽ˜์ด์ง€ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜
3
* data: Data[],
4
* total: number
5
*/
6
async pagePaginatePosts(dto: PaginatePostDto) {
7
const posts = await this.postsRepository.find({
8
skip: dto.take * (dto.page - 1), // 1ํŽ˜์ด์ง€๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋„๋ก
9
take: dto.take,
10
order: {
11
createdAt: dto.order__createAt,
12
},
13
})
14
15
return {
16
data: posts,
17
}
18
}

3. ์‘๋‹ต์— total ํ”„๋กœํผํ‹ฐ ์ถ”๊ฐ€

posts.service.ts
1
// posts.service.ts ์ƒ๋žต
2
/*** ํŽ˜์ด์ง€ ๊ธฐ๋ฐ˜ ํŽ˜์ด์ง€๋„ค์ด์…˜
3
* data: Data[],
4
* total: number
5
*/
6
async pagePaginatePosts(dto: PaginatePostDto) {
7
const [posts, count] = await this.postsRepository.findAndCount({
8
skip: dto.take * (dto.page - 1), // 1ํŽ˜์ด์ง€๋ถ€ํ„ฐ ์‹œ์ž‘ํ•˜๋„๋ก
9
take: dto.take,
10
order: {
11
createdAt: dto.order__createAt,
12
},
13
})
14
15
return {
16
data: posts,
17
total: count,
18
}
19
}