1. Pagination ์ด๋ก
Pagination์ด๋? : ๋ง์ ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ถ์ ์ผ๋ก ๋๋ ์ ๋ถ๋ฌ์ค๋ ๊ธฐ์- ์ฟผ๋ฆฌ์ ํด๋น๋๋ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ํ๋ฒ์ ๋ค ๋ถ๋ฌ์ค์ง ์๊ณ ๋ถ๋ถ์ ์ผ๋ก ์ชผ๊ฐ์ ๋ถ๋ฌ์จ๋ค. e.g.) ํ๋ฒ์ 20๊ฐ์ฉ
- ์ฟ ํก๊ฐ์ ์ฑ์๊ฒฝ์ฐ ์์ต๊ฐ์ ์ํ์ด ๋ฐ์ดํฐ๋ฒ ์ด์ค์ ์ ์ฅ๋์ด ์๋๋ฐ,
- ์ฌ์ฉ์๊ฐ ์ํ ๊ฒ์ ํ๋ฉด์ ๋ค์ด๊ฐ๋๋ง๋ค ๋ชจ๋ ์ํ์ ๋ณด๋ฅผ ์๋ฒ์์ ํด๋ผ์ด์ธํธ๋ก ์ ์กํ ํ์๊ฐ ์๋ค.
- ํ๋ ํด๋ผ์ฐ๋ ์์คํ
์ ๋ฐ์ดํฐ ์ ์ก์ ๋์ด ๋ ๋ค!
- ๋์ด ์๋ค๋๋ผ๋ ์์ต๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ํ๋ฒ์ ๋ณด๋ด๋ฉด ๋ถ๋ช ๋ฉ๋ชจ๋ฆฌ๊ฐ ํฐ์ง๊ฒ์ด๋ค!
- ๋์ด ํฐ์ง์ง ์๋๋ผ๋ ๋ฐ์ดํฐ ์ ์ก์ ์๋นํ ์ค๋ ์๊ฐ์ด ๊ฑธ๋ฆด๊ฒ์ด๋ค!
Pagination์ ๋ํ์ ์ผ๋ก 2๊ฐ์ง๊ฐ ์กด์ฌํ๋ค.
- Page Based Pagination
- Cursor Based Pagination
1.1 Page Based Pagination

- ํ์ด์ง ๊ธฐ์ค์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์๋ผ์ ์์ฒญํ๋ Pagination
- ์์ฒญ์ ๋ณด๋ผ๋ ์ํ๋ ๋ฐ์ดํฐ ๊ฐฏ์์ ๋ช๋ฒ์งธ ํ์ด์ง๋ฅผ ๊ฐ์ ธ์ฌ์ง ๋ช ์
- request๋ก ํ์ด์ง ๋ฒํธ๋ฅผ ๋ณด๋ด๋ฉด, ์ด ํ ํ ํ์ด์ง์ ํ์ฌ ํ์ด์ง ๋ฐ์ดํฐ๋ฅผ ๋ฆฌํดํด์ฃผ๋ ๋ฐฉ์์ด๋ค.
- ํ์ด์ง ์ซ์๋ฅผ ๋๋ฅด๋ฉด ๋ค์ ํ์ด์ง๋ก ๋์ด๊ฐ๋ ํํ์ UI์์ ๋ง์ด ์ฌ์ฉ
- ๋ฌธ์ ์ : Pagination ๋์ค์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ๋ฐ์ดํฐ๊ฐ ์ถ๊ฐ๋๊ฑฐ๋ ์ญ์ ๋ ๊ฒฝ์ฐ,
- ์ ์ฅ๋๋ ๋ฐ์ดํฐ๊ฐ ๋๋ฝ๋๊ฑฐ๋ ์ค๋ณต๋ ์ ์์
- Pagination ์๊ณ ๋ฆฌ์ฆ์ด ๋งค์ฐ ๊ฐ๋จํจ
- 100๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ํ๋ฒ์ 20๊ฐ์ฉ ๊ฐ์ ธ์จ๋ค๋ฉด 5๊ฐ์ ํ์ด์ง๋ก ๋๋ ์ ์๋ค.
1.1.1 Page Based Pagination ๋ฌธ์ ์
(1) ๋ฐ์ดํฐ ์ฝ์

Page Based Pagination์ ๋ฌธ์ ์ ์ ๋ฐ์ดํฐ ์ ๊ท ์ฝ์ ์ ์ค๋ณต๋ ๋ฐ์ดํฐ๊ฐ ๋ฐ์ํ๋ค.
- ์ ๊ทธ๋ฆผ์์ 1ํ์ด์ง์์ ํ๋ฉด์ 1, 2, 3, 4์์ 3.5๋ ์ ๊ท ๋ฐ์ดํฐ๊ฐ ์ฝ์ ๋์๋ค.
- 3.5 ๋ฐ์ดํฐ๊ฐ ์ฝ์ ๋๋ฉด์ 4๋ฒ ๋ฐ์ดํฐ๊ฐ 1ํ์ด์ง์ 2ํ์ด์ง์ ์ค๋ณต๋์ด ๋ํ๋๋ค.
(2) ๋ฐ์ดํฐ ์ญ์

์ด๋ฒ์๋ id=4๊ฐ ์ญ์ ๋๋ค๊ณ ๊ฐ์ ํ๋ฉด, id=5๊ฐ ๋๋ฝ๋๋ค.
1.2 Cursor Based Pagination

- ๊ฐ์ฅ ์ต๊ทผ์ ๊ฐ์ ธ์จ ๋ฐ์ดํฐ๋ฅผ ๊ธฐ์ค์ผ๋ก ๋ค์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ Pagination
- ์์ฒญ์ ๋ณด๋ผ๋ ๋ง์ง๋ง ๋ฐ์ดํฐ์ ๊ธฐ์ค๊ฐ(ID๋ฑ Unique ๊ฐ)๊ณผ ๋ช๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ฌ์ง ๋ช ์
- ์คํฌ๋กค ํํ์ ๋ฆฌ์คํธ์์ ์์ฃผ ์ฌ์ฉ e.g.) ์ฑ์ ListView
- ์ฅ์ : ์ต๊ทผ ๋ฐ์ดํฐ์ ๊ธฐ์ค๊ฐ์ ๊ธฐ๋ฐ์ผ๋ก ์ฟผ๋ฆฌ๊ฐ ์์ฑ๋๊ธฐ๋๋ฌธ์ ๋ฐ์ดํฐ๊ฐ ๋๋ฝ๋๊ฑฐ๋ ์ค๋ณต๋ ํ๋ฅ ์ด ์ ์
- e.g. ๋ง์ง๋ง์ผ๋ก ๋ถ๋ฌ์จ ๋ฐ์ดํฐ๊ฐ 20๋ฒ์ด์๋ค๋ฉด ๋ค์ ์์ฒญ์ 21๋ฒ๋ถํฐ ๊ฐ์ ธ์จ๋ค
(1) ๋ฐ์ดํฐ ์ฝ์

- ํน์ ์ปค์ ์์น๋ถํฐ ๋ค์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๊ธฐ ๋๋ฌธ์ ์ค๋ณต๋ ๋ฐ์ดํฐ ์์ด ์ผ๊ด๋ ์ ๋ณด๋ฅผ ๋ฐ์ ์ ์๋ค.
- ์ฌ์ฉ์๊ฐ ํ์ฌ๊น์ง ๋ก๋ํ ๋ง์ง๋ง ๋ฐ์ดํฐ์ ์์น๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ๋ค์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ ์๋ฒ ๋ถํ๊ฐ ์ค์ด๋ ๋ค.
(2) ๋ฐ์ดํฐ ์ญ์

1.3 ์์ฒญ ํํ
1http://localhost:3000/posts?order__createdAt=ASC&where__id_more_than=3&take=2023// ์ค๋ฆ์ฐจ์์ผ๋ก, id๊ฐ 3๋ณด๋ค ํฐ ๋ฐ์ดํฐ๋ฅผ, 20๊ฐ์ ํฌ์คํธ๋ฅผ ๊ฐ์ ธ์จ๋ค.
{property}__{filter}ํ์์ผ๋ก ๊ตฌํ
order__createAt: ๋ด๋ฆผ/์ค๋ฆ์ฐจ ์ ๋ ฌwhere__id_more_than: ์ด๋ค ID ์ดํ๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ ๊ฑด์งtake: ๋ช ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ ๊ฑด์ง
1.4 ์๋ต ํํ
1{2// ๋ฐ์ดํฐ๋ฅผ ๋ฆฌ์คํธ๋ก ๋ฐ๋ ๋ถ๋ถ3"data": [4{5"id": 4,6"updatedAt": "2023-06-06T15:41:33.928Z",7"createdAt": "2023-06-06T15:41:33.928Z",8"title": "test",9"content": "test123",10"likeCount": 0,11"commentCount": 0,12"author": {13"id": 1,14"nickname": "codefactory5",15"email": "test5@codefactory.ai",16"role": "USER"17}18}19],20// paging ๊ด๋ จ ์ ๋ณด๋ฅผ ์ ๋ ฅํ๋ ๊ณณ21"paging": {22// ๋ค์ ์ปค์์๋ํ ์ ๋ณด23"cursor": {},24"after": 4,25// ์ด ๋ช๊ฐ์ ๋ฐ์ดํฐ๊ฐ ์๋ต์๋์ง26"count": 20,27// ๋ค์ ์์ฒญ URL28"next": "http://localhost:3000/post?order__createdAt=ASC&take=20&where__id__more_than=4"29}30}
2. PaginationPostDto ์์ฑ
posts/dto/paginate-post.dto.ts ํ์ผ์ ๋ง๋ ๋ค.
1import { IsIn, IsNumber, IsOptional } from 'class-validator'23export class PaginatePostDto {4/*** ์ด์ ๋ง์ง๋ง ๋ฐ์ดํฐ์ ID5* ์ด ํ๋กํผํฐ์ ์ ๋ ฅ๋ ID๋ณด๋ค ๋์ ID๋ถํฐ ๊ฐ์ ๊ฐ์ ธ์ค๊ธฐ6*/7@IsNumber()8@IsOptional()9where__id_more_than?: number1011/*** ์ ๋ ฌ12* createdAt : ์์ฑ๋ ์๊ฐ์ ๋ด๋ฆผ์ฐจ/์ค๋ฆ์ฐจ ์์ผ๋ก ์ ๋ ฌ13*/14@IsIn(['ASC']) // ๋ฆฌ์คํธ์ ์๋ ๊ฐ๋ค๋ง ํ์ฉ15@IsOptional()16// eslint-disable-next-line @typescript-eslint/prefer-as-const17order__createAt: 'ASC' = 'ASC'1819/*** ๊ฐ๊ณ ์ฌ ๋ฐ์ดํฐ ๊ฐ์20* ๋ช ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์๋ต์ผ๋ก ๋ฐ์์ง21*/22@IsNumber()23@IsOptional()24take: number = 2025}
posts ์ปจํธ๋กค๋ฌ์ getPosts๋ฅผ ์์ ํ๋ค.
1// posts.controller.ts ์๋ต2@Get()3getPosts(4@Query() query: PaginatePostDto, //5) {6return this.postsService.getAllPosts()7}
3. MoreThan๊ณผ Order๋ก ํํฐ๋ง
posts ์๋น์ค์ paginatePosts์ ์์ฑํ๋ค.
1// posts.service.ts ์๋ต2/***3* 1) ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๋ ฌํ๋ pagination๋ง ๊ตฌํํ๋ค4*/5async paginatePosts(dto: PaginatePostDto) {6const posts = await this.postsRepository.find({7where: {8// ๋ ํฌ๋ค / ๋ ๋ง๋ค9id: MoreThan(dto.where__id_more_than ?? 0),10},11order: {12createdAt: dto.order__createAt,13},14take: dto.take,15})1617/*** Response18* data : Data[],19* cursor : {20* after: ๋ง์ง๋ง Data์ ID21* }22* count: ์๋ตํ ๋ฐ์ดํฐ์ ๊ฐ์23* next: ๋ค์ ์์ฒญ์ ํ ๋ ์ฌ์ฉํ URL24*/25return {26data: posts,27}28}
posts ์ปจํธ๋กค๋ฌ์์ ์์ฑํ paginatePosts๋ฅผ ์ ์ฉํ๋ค.
1// posts.controller.ts2/*** 1) GET /posts3* ๋ชจ๋ post๋ฅผ ๋ค ๊ฐ์ ธ์จ๋ค4*/5@Get()6getPosts(7@Query() query: PaginatePostDto, //8) {9return this.postsService.paginatePosts(query)10}
์ ๋ ฌ ์ต์
์ด ์ ๋๋ก ๋์ํ๋ ค๋ฉด main.ts์ tranform ์ต์
์ true๋ก ๋ฐ๊ฟ์ค์ผ ํ๋ค.
1// ์๋ต2async function bootstrap() {3const app = await NestFactory.create(AppModule)4app.useGlobalPipes(5new ValidationPipe({6transform: true,7}),8)9await app.listen(3000)10}11bootstrap()
- ์ด๋ ๊ฒ ํด์ฃผ๋ ์ด์ ๋ posts ์ปจํธ๋กค๋ฌ์์ ๋ง์ฝ query์ ๊ฐ์ ์ค์ ๋ก ๋ฃ์ ์ ์ด ์๋ค๋ฉด,
- ๋ฃ์ง ์์์ผ๋๊น, ๋ฃ์ง ์์ ๊ฐ์ ๊ทธ๋๋ก ๋ฐํํด์ค๋ค.
- ์ด๊ฒ classValidator์ classTransformer๊ฐ ๊ธฐ๋ณธ์ ์ผ๋ก ์๋ํ๋ ๋ฐฉ์์ด๋ค.
- ๊ทธ๋ฐ๋ฐ ๊ฐ์ ๋ฃ์ง ์๋๋ผ๋ ์ ์ธํ ๊ธฐ๋ณธ ๊ฐ๋ค์ ๋ฃ์ ์ฑ๋ก, ํด๋์ค๋ฅผ ๋ณํํด DTO๋ฅผ ํ์ฑํด ์คฌ์ผ๋ฉด ํ๊ธฐ ๋๋ฌธ์
- main.ts์์
transform : true๋ฅผ ๋ฃ์ด์ค์ผ๋ก์จ - ๊ฐ์ ๋ฃ์ง ์์๋ค๋ฉด ๋ํดํธ ๊ฐ๋ค์ด DTO์๋ค๊ฐ ๋ฃ์ ์ฑ๋ก ์ธ์คํด์ค๋ฅผ ์์ฑํด๋ ๋๋ค๊ณ ํ๊ฐ๋ฅผ ํด์ฃผ๋ ๊ฒ.
- ์ฆ, ๋ณํํ๋ ์์ ์ ํด๋ ๊ด์ฐฎ๋ค๋ผ๋ ์ต์ ์ ๋ฃ์ด์ค ๊ฒ์ด๋ค.
- main.ts์์
4. ๋๋ค ๋ฐ์ดํฐ ์์ฑํ๋ ๋ก์ง ๋ง๋ค๊ธฐ
ํ์ด์ง๋ค์ด์ ์ ํ ์คํธํ ์ ์๋๋ก, ์์๋ก ์ถฉ๋ถํ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๋ ํจ์๋ฅผ posts ์๋น์ค์ ๋ง๋ ๋ค.
1// posts.service.ts2/*** ํ์ด์ง๋ค์ด์ ์ฉ ํ ์คํธ ํฌ์คํธ ์์ฑ3*4*/5async generatePosts(userId: number) {6for (let i = 0; i < 100; i++) {7await this.createPost(userId, {8title: `์์๋ก ์์ฑ๋ ํฌ์คํธ ์ ๋ชฉ ${i}`,9content: `์์๋ก ์์ฑ๋ ํฌ์คํธ ๋ด์ฉ ${i}`,10})11}12}
๊ทธ๋ฆฌ๊ณ ํ์ด์ง๋ค์ด์ ํ ์คํธ์ฉ ์๋ํฌ์ธํธ๋ฅผ posts ์ปจํธ๋กค๋ฌ์ ์ถ๊ฐํ๋ค.
1// posts.controller.ts ์๋ต2/*** POST /posts/random3*4*/5@Post('random')6@UseGuards(AccessTokenGuard)7async postPostsRandom(@User() user: UsersModel) {8await this.postsService.generatePosts(user.id)9return true10}
- cf. ํ๋ก๋์ (์ค์ ๋ฐฐํฌ) ๋จ๊ณ์์๋ ์ด๋ฐ ํ ์คํธ API๋ค์ ๋ฐ๋์ ์ง์์ค์ผ ํ๋ค.
- ํฌ์คํธ๋งจ์์ ํ ์คํธ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๋ค.
5. Type Annotation ์ฌ์ฉ & Implicit Conversion ์ ์ฉ
- URL์ ๋ณด๋ด๋ ๋ชจ๋ ๋ฐ์ดํฐ๋ String์ผ๋ก ๋ฐ๋๋ค.
- ๊ทธ๋์ URL์ string์ผ๋ก ๋ค์ด์จ ๊ฐ์ number๋ก ๋ณํํด์ค์ผ ํ๋ค.
paginate-post.dto.ts์ Type ๋ณํ ์ด๋
ธํ
์ด์
์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค.
1import { Type } from 'class-transformer'2import { IsIn, IsNumber, IsOptional } from 'class-validator'34export class PaginatePostDto {5/*** ์ด์ ๋ง์ง๋ง ๋ฐ์ดํฐ์ ID6* ์ด ํ๋กํผํฐ์ ์ ๋ ฅ๋ ID๋ณด๋ค ๋์ ID๋ถํฐ ๊ฐ์ ๊ฐ์ ธ์ค๊ธฐ7*/8@Type(() => Number) // URL์ string์ผ๋ก ๋ค์ด์จ ๊ฐ์ number๋ก ๋ณํํด์ค9@IsNumber()10@IsOptional()11where__id_more_than?: number1213// ์๋ต14}
๊ทผ๋ฐ ์ด๋ ๊ฒ Type ์ด๋ ธํ ์ด์ ๋ง๊ณ , main.ts์์ ์ต์ ์ ์ด์ฉํด์ค ์๋ ์๋ค.
- ์์์ ์ถ๊ฐํ
@Type(() => Number)๋ฅผ ์ง์ด๋ค. main.ts๋ฅผ ๋ค์๊ณผ ๊ฐ์ด ์์ ํ๋ค.
1// main.ts ์๋ต2async function bootstrap() {3const app = await NestFactory.create(AppModule)4app.useGlobalPipes(5new ValidationPipe({6transform: true,7transformOptions: {8// ์์๋ก ๋ณํ์ ํ๊ฐ9enableImplicitConversion: true,10},11}),12)13await app.listen(3000)14}15bootstrap()
- ์ด๋ ๊ฒ true๋ก ์ต์ ์ ์ค์ ํด์ฃผ๋ฉด,
paginate-post.dto์ ๊ฐ์ ํ์ผ์ IsNumber ์ด๋ ธํ ์ด์ ์ด ์์ผ๋ฉด,- ์ซ์๋ก ๋ณํ๋๋ ๊ฒ์ด ์ ์์ธ์ง ์์์ ํ๋จํ๊ณ , ์๋์ผ๋ก ์ซ์๋ก ๋ณํํ๋ค.
- ์ฆ, ์ ๋ ฅ๋ type์ ๊ธฐ์ค์ผ๋ก ์๋์ผ๋ก class-transformer๊ฐ ์์ฉํ๋๋ก ํ ์ ์๋ค.
6. CursorPagination ๋ฉํ๋ฐ์ดํฐ ์์ฑ
common/const/env.const.ts ํ์ผ์ ๋ง๋ ๋ค.
1export const PROTOCOL = 'http'2export const HOST = 'localhost:3000'
posts ์๋น์ค์ paginatePosts๋ฅผ ์์ ํ๋ค.
1// posts.service.ts ์๋ต2/***3* 1) ์ค๋ฆ์ฐจ์์ผ๋ก ์ ๋ ฌํ๋ pagination๋ง ๊ตฌํํ๋ค4*/5async paginatePosts(dto: PaginatePostDto) {6const posts = await this.postsRepository.find({7where: {8// ๋ ํฌ๋ค / ๋ ๋ง๋ค9id: MoreThan(dto.where__id_more_than ?? 0),10},11order: {12createdAt: dto.order__createAt,13},14take: dto.take,15})1617/****18* ํด๋น๋๋ ํฌ์คํธ๊ฐ 0๊ฐ ์ด์์ด๋ฉด, ๋ง์ง๋ง ํฌ์คํธ๋ฅผ ๊ฐ์ ธ์ค๊ณ19* ์๋๋ฉด null์ ๋ฐํํ๋ค.20*/21const lastItem = posts.length > 0 ? posts[posts.length - 1] : null22const nexttUrl = lastItem && new URL(`${PROTOCOL}://${HOST}/posts`)2324/**** dto์ ํค๊ฐ๋ค์ ๋ฃจํํ๋ฉด์25* ํค๊ฐ์ ํด๋น๋๋ ๋ฒจ๋ฅ๊ฐ ์กด์ฌํ๋ฉด, parame์ ๊ทธ๋๋ก ๋ถ์ฌ๋ฃ๋๋ค.26* ๋จ, where__id_more_than ๊ฐ๋ง lastItem์ ๋ง์ง๋ง ๊ฐ์ผ๋ก ๋ฃ์ด์ค๋ค.27*/28if (nexttUrl) {29for (const key of Object.keys(dto)) {30if (dto[key]) {31if (key !== 'where__id_more_than') {32nexttUrl.searchParams.append(key, dto[key])33}34}35}36nexttUrl.searchParams.append('where__id_more_than', lastItem.id.toString())37}3839/*** Response40* data : Data[],41* cursor : {42* after: ๋ง์ง๋ง Data์ ID43* }44* count: ์๋ตํ ๋ฐ์ดํฐ์ ๊ฐ์45* next: ๋ค์ ์์ฒญ์ ํ ๋ ์ฌ์ฉํ URL46*/47return {48data: posts,49cursor: {50after: lastItem?.id,51},52count: posts.length,53nest: nexttUrl?.toString(),54}55}
7. ๋ง์ง๋ง ํ์ด์ง ๋ก์ง ์กฐ๊ฑด ์ถ๊ฐ
1// posts.service.ts์ paginatePosts ์๋ต2/****3* ํด๋น๋๋ ํฌ์คํธ๊ฐ 0๊ฐ ์ด์์ด๋ฉด, ๋ง์ง๋ง ํฌ์คํธ๋ฅผ ๊ฐ์ ธ์ค๊ณ4* ์๋๋ฉด null์ ๋ฐํํ๋ค.5*/6const lastItem = posts.length > 0 && posts.length === dto.take ? posts[posts.length - 1] : null7const nexttUrl = lastItem && new URL(`${PROTOCOL}://${HOST}/posts`)
8. ๋ค์ ์ปค์๊ฐ ์กด์ฌํ์ง ์์ ๋ undefined๋์ null
1// posts.service.ts์ paginatePosts ์๋ต2/*** Response3* data : Data[],4* cursor : {5* after: ๋ง์ง๋ง Data์ ID6* }7* count: ์๋ตํ ๋ฐ์ดํฐ์ ๊ฐ์8* next: ๋ค์ ์์ฒญ์ ํ ๋ ์ฌ์ฉํ URL9*/10return {11data: posts,12cursor: {13after: lastItem?.id ?? null,14},15count: posts.length,16nest: nexttUrl?.toString() ?? null,17}
9. ๋ด๋ฆผ์ฐจ์ Next ํ ํฐ ๋ก์ง ์์ฑ
paginate-post.dto.ts์ ๋ด๋ฆผ์ฐจ์์ ์ํ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค.
1import { IsIn, IsNumber, IsOptional } from 'class-validator'23export class PaginatePostDto {4@IsNumber()5@IsOptional()6where__id_less_than?: number78/*** ์ด์ ๋ง์ง๋ง ๋ฐ์ดํฐ์ ID9* ์ด ํ๋กํผํฐ์ ์ ๋ ฅ๋ ID๋ณด๋ค ๋์ ID๋ถํฐ ๊ฐ์ ๊ฐ์ ธ์ค๊ธฐ10*/11@IsNumber()12@IsOptional()13where__id_more_than?: number1415/*** ์ ๋ ฌ16* createdAt : ์์ฑ๋ ์๊ฐ์ ๋ด๋ฆผ์ฐจ/์ค๋ฆ์ฐจ ์์ผ๋ก ์ ๋ ฌ17*/18@IsIn(['ASC', 'DESC']) // ๋ฆฌ์คํธ์ ์๋ ๊ฐ๋ค๋ง ํ์ฉ19@IsOptional()20order__createAt: 'ASC' | 'DESC' = 'ASC'2122/*** ๊ฐ๊ณ ์ฌ ๋ฐ์ดํฐ ๊ฐ์23* ๋ช ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์๋ต์ผ๋ก ๋ฐ์์ง24*/25@IsNumber()26@IsOptional()27take: number = 2028}
posts ์๋น์ค์ ๋ด๋ฆผ์ฐจ์ ์ฝ๋๋ฅผ ์ถ๊ฐํ๋ค.
1// posts.service.ts์ paginatePosts ์๋ต2/**** dto์ ํค๊ฐ๋ค์ ๋ฃจํํ๋ฉด์3* ํค๊ฐ์ ํด๋น๋๋ ๋ฒจ๋ฅ๊ฐ ์กด์ฌํ๋ฉด, parame์ ๊ทธ๋๋ก ๋ถ์ฌ๋ฃ๋๋ค.4* ๋จ, where__id_more_than ๊ฐ๋ง lastItem์ ๋ง์ง๋ง ๊ฐ์ผ๋ก ๋ฃ์ด์ค๋ค.5*/6if (nexttUrl) {7for (const key of Object.keys(dto)) {8if (dto[key]) {9if (key !== 'where__id_more_than' && key !== 'where__id_less_than') {10nexttUrl.searchParams.append(key, dto[key])11}12}13}14let key = null15if (dto.order__createAt === 'ASC') {16key = 'where__id_more_than'17} else {18key = 'where__id_less_than'19}20nexttUrl.searchParams.append(key, lastItem.id.toString())21}
10. ๋ด๋ฆผ์ฐจ์ ์ ๋ ฌ Where ์ฟผ๋ฆฌ ์์ฑ
1// posts.service.ts์ paginatePosts ์๋ต2async paginatePosts(dto: PaginatePostDto) {3const where: FindOptionsWhere<PostsModel> = {}45if (dto.where__id_less_than) {6where.id = LessThan(dto.where__id_less_than)7} else if (dto.where__id_more_than) {8where.id = MoreThan(dto.where__id_more_than)9}1011const posts = await this.postsRepository.find({12where,13order: {14createdAt: dto.order__createAt,15},16take: dto.take,17})1819// ์๋ต20}