테스트코드와 마찬가지로, 나에게 REST API는 아픈 손가락이였다. (한개 더 있는데, 오늘내로 공부하는게 목표다.)
GraphQL가 더 좋다고 생각하고 있지만, 회사가 처음부터 사용을 했을 경우에 속하는 것이기 때문에
일반적으로 사용하는 REST API의 사용법도 알아야했기 때문인데.
커리큘럼상 GraphQL에 비중을 높게 잡아놔서, 제대로 사용해보지 못했다.
그래서 걱정이 있다가 과제테스트의 느낌이 REST API를 사용해야한다. 라는 조건이 나올 것 같다는 생각이 들었다.
결국은 과제테스트를 위해서 & 여러가지를 사용할 수 있는 나를 위해서 작성하는 글이다.
같은 동기생이 나에게 과제에 대한 검사(?)를 요청받았던 적이 있었는데
내가 바빠서 답변을 주지 못했는데, 주석도 잘 적혀있고 잘하시는 분이라 이것으로 공부를 할 예정이다.
(미안합니다,,,,취업 축하해요..^^ 와 허락받았다~)
그리고 이제 어느정도 타인의 코드를 보면 읽을 수 있는 수준이 된 것 같아서 할만할 것 같다.
GraphQL같은 경우에는 플레이그라운드라는 것을 통해서, 자동으로 API Docs를 만들어준다.
근데 내가 REST API를 처음 사용했을 무렵에는 자동화가 되는지 몰랐다.
덕분에 swagger를 직접 작성하면서 API Docs를 만들었던 기억이 있는데
코딩이라는 것을 처음 했던 시기에 스페이스바 한칸 차이로 에러가 발생하는 모습을 보면서
정말 괴롭고 고통스러운 시간이었기에 다시는 하고 싶지 않아!!!!! 라고 생각을 했던 것 같다(ㅋㅋ)
근데 동기분이 나에게 연락을 주면서 데코레이터를 붙이면 문서 자동화가 된다길래
어?
그럼...괜찮겠는데 ? 써봐야겠다 라는 생각을 가지고 있었다.
원래는 새로운 페이지를 만들어주려면 이것저것 다양한 작업을 해줘야한다. HTML CSS JS 등등등
하지만 우리는 이미 완성되어있는 곳에 라우팅을 하는 것으로 몇가지 정보만 바꿔서 적용을 시킬 수 있다.
그래서 라이브러리에 있는 옵션으로 문서를 만들 수 있는데, 아래와 같다.
src/apis/utils/swagger.ts
import { INestApplication } from '@nestjs/common';
import {
SwaggerModule,
DocumentBuilder,
SwaggerCustomOptions,
} from '@nestjs/swagger';
const swaggerCustomOptions: SwaggerCustomOptions = {
swaggerOptions: {
persistAuthorization: true,
},
};
export function setUpSwagger(app: INestApplication): void {
const options = new DocumentBuilder()
.setTitle('좌충우돌 뮤테이션의 연습 페이지')
.setDescription('뮤테이션이 만들지 않은 API 입니다.')
.setVersion('1.0.0')
.build();
const document = SwaggerModule.createDocument(app, options);
SwaggerModule.setup('api-docs', app, document, swaggerCustomOptions);
}
위에 있는 new DocumentBuilder()에 적혀있는 옵션들이 똑같이 입력되어있는 모습을 볼 수 있다.
이 모양을 보면서 어떤 생각이 들었냐면, 과거에 AdminBro로 만들었던 관리자 페이지와 비슷한 구조로 되어있는 것 같다는 생각이 들었다.
결국 완성되어있는 것에 몇개를 넣어가지고 하는 것이라 그런가보다.
여기서도 뭔가 모르겠는게 보여서 찾아봤는데
그 외에는 별다른 것이 없어서 다음 챕터로-
NestJS는 포트번호가 3000번이 기본값이라서 보통 localhost:3000을 통해서 접속한다.
GraphQL은 localhost:3000/GraphQL
로 이동할 경우 플레이그라운드로 넘어갈 수 있는데
swagger같은 경우에도 localhost:3000/api-docs가 기본값이지만, 코드를 보니 뒤에 치기 귀찮아서(?)그냥 넘겨버린 것으로 확인된다.
src/app.controller.ts
@ApiExcludeController()
@Controller()
export class AppController {
constructor(private readonly appService: AppService) {}
@Get()
@ApiExcludeEndpoint()
sendSwaggerDocs(@Res() res: ExpressResponse): void {
this.appService.sendSwaggerDocs(res);
}
}
src/app.service.ts
@Injectable()
export class AppService {
async sendSwaggerDocs(res: ExpressResponse) {
res.redirect(302, 'http://localhost:3000/api-docs');
}
}
여기서 처음보는 두개의 데코레이터를 봐서 같이 설명을 하려고 한다.
(사실 다 처음보겠지 안써봤으니까..)
왜 숨길까, 생각을 해봤는데 작업이 덜 되었는데 누가 건드릴까봐 사용하지 않을까? 라는 생각을 가지고 있다.
그리고 Res의 타입에 ExpressResponse가 선언되어있는데, 음... 보안상의 문제로 선언을 해놓은 것인가? 라는 생각이 든다.
입력값이 아니라, 리턴값으로 사용하는 것이라서 뭔가 물음표가 쪼끔 띄워지는 느낌. (any 쓰니까 되긴 하네ㅋㅋ)
아시는 분이 있다면 정보 댓글 부탁드려요.
Swagger는 단순히 API 문서의 역할만 하는 것이 아니라, 실제로 API를 호출할 수 있는 기능이 존재한다.
그렇다는 소리는 API 성공 및 실패 여부 그리고 기본값 결과값을 모조리 다 적어줘야한다는 것과 동일하다(....)
그래서 과거에 아래같은 문서를 만들었던 경험이 있다.
이거 공백 하나 틀리면 에러난다(.....)
하지만 라이브러리를 통해서 저런 문서를 작성할 필요는 없고, 데코레이터에 값을 달아주는 것으로 똑같이 적용시킬 수 있다.
그래서 문득 생각이 든 것인데, 이게 자동화가 맞나....? 아닌 것 같은데....?
코드를 보면서 알아가보자!
@Patch(':reviewId')
@ApiOperation({
summary: '리뷰 수정',
description: '리뷰를 수정합니다',
})
@ApiBody({ type: UpdateReviewDto })
@ApiParam({ type: String, name: 'reviewId' })
@ApiResponse({
status: 200,
description: '리뷰 수정 성공',
type: EventDto,
})
@ApiBadRequestResponse({
description: '리뷰가 존재하지 않는경우.',
status: 400,
})
update(
@Param('reviewId') reviewId: string,
@Body(ValidationPipe) updateReviewDto: UpdateReviewDto,
): Promise<EventDto> {
return this.reviewService.update(reviewId, updateReviewDto);
}
나처럼 GraphQL에 절여진 사람이라면, 모든 것이 새롭다.
지금까지는 @Query, @Mutation으로만 사용했다면 다시 기본으로 돌아가서 HTTP의 기본 메소드와 친구를 해야한다.
왜나햐면 RESTful----한 API를 만들기 위해서는 무조건 지켜줘야하는 사항이다.
어제 모든 API 요청을 Post로 해버리는거에 대한 토론이 열린 것으로 기억하는데 ㅋㅋ
최대한 맞춰주는게 좋다! 라고 많은 사람들이 이야기를 하고 있다.
엔드포인트도 지정을 해줘야한다.
GraphQL의 경우에는 엔드포인트가 1개밖에 없었는데, 이제 무수히 많이 늘어나면서 그것에 대한 명시를 해줘야한다.
위의 API경우에는 :
를 사용하는 것으로 동적인 값이 들어올 수 있도록 해놓은 것이다.
그 외의 사항은 한번에 정리한다.
사진으로 보면 어디가 어떻게 적용되는지 확인할 수 있다.
이제 어느정도 이해가 됐다. 문서화를 만들기 위해서는 어떤 작업을 해야하며
에러핸들링을 해줄 때는 어떤 데코레이터를 사용해야하는지 감이 좀 잡힌 것 같다.
하지만 아직 해결해야하는 문제가 있는데, 그것은 바로 Swagger에서 API 테스트를 위하여
디폴트값이 지정되어있는 것이 편하기에 그 부분에 대한 정보가 필요했다.
그래서 찾아보니, Entity에 그러한 내용
이 붙어있었다.
어떻게 적용을 시키나 찾아봤더니, 생각보다는 단순하게 하는 것을 볼 수 있었다.
근데 이게 자동화라고 불러야하나... 정말 하드코딩을 다 해놔야하는 웃긴 상황을 볼 수 있었다.
@Entity()
export class Review {
@ApiProperty({
example: 'UUID',
description: '리뷰의 ID (UUID)',
required: true,
})
@PrimaryGeneratedColumn('uuid')
id: string;
@ApiProperty({
example: '오사카가 재미있네요',
description: '리뷰의 내용',
required: true,
})
@Column({ type: 'longtext', nullable: false })
content: string;
@ApiPropertyOptional({ type: () => [ReviewImage] })
@OneToMany(() => ReviewImage, (reviewImage) => reviewImage.review, {
nullable: true,
cascade: true,
})
images?: ReviewImage[];
}
기본값 설정 데코레이터는 두개가 존재하였으며, 사용목적이 명확했다.
그리고 관계가 존재하는 프로퍼티같은 경우에는 Entity를 따라서 들어가보면 거기에도 결국 ApiProperty()로 값이 입력되어있는 것을 봤다.
자동화아님 진짜 이거
생각보다는 사용하는 데코레이터들이 명확했고, 정보도 많이 있던터라 코드를 읽어나가는데 문제가 없었고
바로 도입을 하더라도 충분히 사용할 수 있는 것 같다는 생각이 들었다.
아쉬운 것은 캐시를 도입하는 것에 대한 글을 확인했는데, 도대체 어떻게 적용을 해야할지 모르겠다는 것?
그리고 그러한 작업을 하기 위해서는 또다른 프로젝트라던가 간단한 CRUD 기획이 필요한데 그럴 여유가 없다는 것이였다(...)
뭐 로깅도 해보고 싶어서 좀 찾아보고 있는데, 이것도 또 복잡한 이야기고 중요한 것이라 대충 보고 따라할 수는 없을 것 같아서 계---속 찾아봐야 할 것 같다.
아, 클라우드 스토리지를 안쓰고 multer를 사용한 이미지 업로드도 좀 찾아봐야하는데 뭐이리 할게 많지.....?
다음에 쓸 것은 NestJS에서 NoSQL MongoDB를 사용하는 방법에 대해서다.
끝