* 설계
/**
* Response
*
* data : Data[],
* cursor : {
* after: 마지막 Data의 ID
* }
* count : 응답한 데이터의 갯수
* next : 다음 요청을 할때 사용할 URL
*/
* dto 생성
- dto
- main에서 transform true 해줘야 기본값이 반영됨.
import { IsIn, IsNumber, IsOptional } from "class-validator";
export class PaginatePostDto {
/**
* 이전 마지막 데이터의 ID
* 이것 이후로 데이터를 가져와야함
*/
@IsNumber()
@IsOptional()
where__id_more_than?:number;
@IsIn(['ASC', 'DESC']) //값제한
@IsOptional()
order__createdAt?: 'ASC' = 'ASC' ; //기본값은 ASC
/**
* 몇개의 데이터를 가져올건지
*/
@IsNumber()
@IsOptional()
take: number=20;
}
app.useGlobalPipes(new ValidationPipe({
transform: true, //dto를 수정 가능하게(dto 기본값 들어가도록)
}));
- 컨트롤러
@Get()
getAllPosts(
@Query() query : PaginatePostDto
){
return this.postsService.getAllPosts();
}
- 서비스 구현중
- dto.id가 빌경우,?? 로 기본값 줘야함에 주의!
async paginatePosts(dto : PaginatePostDto){
const posts = await this.postsRepository.find({
where:{
id: MoreThan(dto.where__id_more_than ?? 0), //없으면 기본값 0으로!
},
order:{
createdAt: dto.order__createdAt,
},
take: dto.take,
});
/**
* Response
*
* data : Data[],
* cursor : {
* after: 마지막 Data의 ID
* }
* count : 응답한 데이터의 갯수
* next : 다음 요청을 할때 사용할 URL
*/
return {
data : posts,
// cursor : {
// after : posts[posts.length-1].id,
// },
count : posts.length,
next : "",
}
}
- 컨트롤러
- pageDto는 query로 받는게 관례적임
@Get()
getAllPosts(
@Query() query : PaginatePostDto
){
return this.postsService.paginatePosts(query);
}
* test용 데이터 생성
@Post('random')
@UseGuards(AccessTokenGuard)
async postPostRandom(@User() user: UsersModel){
await this.postsService.generatePosts(user.id);
return true;
}
async generatePosts(userId : number){
for(let i=0;i<100;++i){
await this.createPost(userId, {
title : `임의로 생성된 제목 ${i}`,
content : `임의로 생성된 내용 ${i}`,
})
}
}
* Type Annotation
- 문제 : url은 기본적으로 string임 => 엔티티의 @IsNumber와 충돌
- 해결1 : @Type => 검사전에, 강제형변환
export class PaginatePostDto {
/**
* 이전 마지막 데이터의 ID
* 이것 이후로 데이터를 가져와야함
*/
@Type(()=>Number)
@IsNumber()
@IsOptional()
where__id_more_than?:number;
- 해결2 : main에서 enable옵션 켜주기 (추천!)
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe({
transform: true, //dto를 수정 가능하게(dto 기본값 들어가도록)
transformOptions:{
enableImplicitConversion: true, //Class-Validator Type에 맞게 자동형변환
}
}));
await app.listen(3000);
}
bootstrap();
- dto의 어노테이션에 맞춰서 형변환 해줌.
*/
@IsNumber()
@IsOptional()
where__id_more_than?:number;
* cnt, cursor, next 값 response 구현
async paginatePosts(dto : PaginatePostDto){
const posts = await this.postsRepository.find({
where:{
id: MoreThan(dto.where__id_more_than ?? 0), //없으면 기본값 0으로!
},
order:{
createdAt: dto.order__createdAt,
},
take: dto.take,
});
/**
* Response
*
* data : Data[],
* cursor : {
* after: 마지막 Data의 ID
* }
* count : 응답한 데이터의 갯수
* next : 다음 요청을 할때 사용할 URL
*/
const lastItem = posts.length > 0 ? posts[posts.length-1] : null;
//lastItem이 존재하는 경우에만
const nextUrl = lastItem && new URL(`${PROTOCOL}://${HOST}/posts`);
if(nextUrl){
/**
* dto 의 키값들 돌면서 (id, order, take)
* param 채우기
*/
for(const key of Object.keys(dto)){
if(dto[key]){ //값이 있는지 체크
if(key !== 'where__id__more_than'){ //나머지 속성들 넣어주고
nextUrl.searchParams.append(key, dto[key]); //order=ASC&take=20
}
}
}
//마지막으로 id 넣어주기 (req(dto)에 id 입력안한경우도 작동해야함)
//where__id=20
nextUrl.searchParams.append('where__id__more_than', lastItem.id.toString());
}
return {
data : posts,
cursor : {
after : lastItem.id,
},
count : posts.length, //null인경우 실행안됨.
next : nextUrl.toString(), //toString으로 객체를 str로 바꿔야 표시됨!
}
}
* 문제
- 마지막 페이지인경우에도 next를 반환하고있음
- 해결 : lastItem을 가져오는 조건추가
//가져온 게시물의길이 === 가져와야할 게시글 -> 정상작동
// 아닌경우 : 마지막페이지임 -> lastItem = null
const lastItem = posts.length > 0 && posts.length === dto.take ? posts[posts.length-1] : null;
- return 시 ?로 예외처리 필수!
return {
data : posts,
cursor : {
after : lastItem?.id, //null인경우 실행안됨 예외처리!.
},
count : posts.length, //null인경우 실행안됨.
next : nextUrl?.toString(), //toString으로 객체를 str로 바꿔야 표시됨!
}
- ?? 를 이용해서 null로 명시해주기
return {
data : posts,
cursor : {
after : lastItem?.id ?? null, //null인경우 실행안됨 예외처리!.
},
count : posts.length, //null인경우 실행안됨.
next : nextUrl?.toString() ?? null, //toString으로 객체를 str로 바꿔야 표시됨!
}
* 내림차순 페이징 구현
- dto 추가
/**
* DESC와 짝궁
*/
@IsNumber()
@IsOptional()
where__id_less_than?:number;
- where 객체 만들기
- dto의 정렬 옵션에 따른 분기
async paginatePosts(dto : PaginatePostDto){
//where 객체 만들기
//typeorm 코드 긁어오기 => 자동완성, typeSafe 보장받음
const where : FindOptionsWhere<PostsModel> = { }
if(dto.where__id_less_than){
where.id = LessThan(dto.where__id_less_than);
}
else{
where.id = LessThan(dto.where__id_more_than);
}
const posts = await this.postsRepository.find({
where,
order:{
createdAt: dto.order__createdAt,
},
take: dto.take,
});
'JS > Nest.js' 카테고리의 다른 글
[Nest] 이미지 업로드 구현 V1 (0) | 2024.09.22 |
---|---|
[Nest] 환경변수 .env 구현 (0) | 2024.09.22 |
[Nest] @Exclude => res에 비밀번호 제외, 직렬화 (0) | 2024.09.20 |
[Nest] DTO 구현, class-validator, message 구현 (0) | 2024.09.20 |
[Postman] 자동으로 accessToken 주입 설정, authorization 설정 (0) | 2024.09.20 |