오늘 한 일
오늘은 어제 공부했던 NestJS 부분이 이해가 잘 되지 않아 추가적으로 노마드코더 강의를 들으며 기본 CRUD 구현에 대해 배웠다. 그리고 프로젝트를 시작했는데, api 명세서 작성 및 ERD를 작성했다.
API 명세서
https://docs.google.com/spreadsheets/d/1rT34ONPqngH7swocxEmefstqsa4cLM1zAADa-__wxlc/edit#gid=0
ERD
https://drawsql.app/teams/-740/diagrams/reservation-performance-project
reservation_performance_project | DrawSQL
Database schema diagram for reservation_performance_project.
drawsql.app
배운 부분
Nest.js
- 우리는 기본 세팅에서 nest new <프로젝트 이름>을 통해 기본적인 폴더들을 세팅했었다(controller, service까지도 생성되었다)
- 하지만 컨트롤러와 서비스 모두를 지우고 시작해 볼 것이다.
- 폴더들을 Nest의 기본적인 키워드로 생성해 볼 것인데, 그 중 하나가 generate이다. generate를 이용해 커맨드 라인으로 NestJS의 거의 모든 것을 생성할 수 있다.
nest g co movies // controller 생성
nest g
- 이렇게 생성하게 된다면 app.module.ts는 movies 컨트롤러를 이미 자동으로 import 해주고 모듈에 엮어놨다.
- 컨트롤러에서 웹 페이지를 만드는 함수를 만들어 볼건데, 알아야 할 점이 있다.
@Controller('movies')
export class MoviesController {
@Get()
getAll() {
return 'this will return all movies';
}
이 코드에서 @Controller('movies')라고 작성되어 있다면 @Get에 아무런 경로가 없다고 하더라도 이 부분이 컨트롤러만을 위한 url을 만든다.
결론적으로 @Controller()안에 들어있는 내용이 url의 Entry Point를 컨트롤 한다는 의미이다. (기본경로가 된다는 의미)
- param 가져오기
@Get(':id')
getOne(@Param('id') movieId: string) {
return `this will return one movie with the id: ${id}`;
}
get에서 주어진 경로로 id값을 param으로 가져온다고 했을 때 @Param(”id”) id:string 이라는 것을 통해 param에 들어있는 id 값을 문자열형태의 id라는 변수에 담아주는 것이라고 생각하면 된다. 이 때, @Get에서 사용하는 id 라는 변수와 @Params에서 사용하는 id 라는 변수는 같아야 한다.
- crud 기능 구현 파라미터 경로로 만든 것보다 코드가 아래 적혀 있는 하위 경로의 경우 nest에서 임의적으로 id값으로 판단하기 때문에 파라미터 경로보다 위에 적어줘야 한다.
- 쿼리 가져오기
@Get('search')
search(@Query('year') searchingYear: string) {
return `We are searching movie made after: ${searchingYear}`;
}
쿼리 안에는 쿼리에 써놓은 변수를 넣어줘야 한다. searchingYear은 마찬가지로 내가 받고 싶은 변수 아무 이름으로나 받으면 된다.
- 서비스 만들기 우선 private movies를 지정해줬는데 우리는 형식을 지정해줘야 한다. 따라서 같은 movies 폴더 안에 entities폴더를 생성하고 movie.entity.ts 파일을 만든다.
movie.entity.ts에서는 서비스로 보내고 받을 클래스(인터페이스)를 export할 것임. 따라서 여기서는 movie를 구성하는 그 자체를 보내주는 것임. 여기서는 javascript object를 쓸 건데 보통은 entities에 실제 데이터 베이스의 모델을 만들어야 한다.
- controller에서 service와 이어주기 위해 controller 안에 constructor로 service와 이어준다.
@Controller('movies')
export class MoviesController {
constructor(private readonly moviesService: MoviesService) {}
이렇게 만들면 service의 클래스, 메서드들을 가져다 쓸 수 있다.
@Get()
getAll(): Movie[] {
return this.moviesService.getAll();
}
- Nest JS에서 자동으로 제공하는 에러 던지는 기능
throw new NotFoundException(`movie with ID ${id} not found`);
- updateData와 movieData에게 타입을 부여하기 위해 서비스와 컨트롤러에서 DTO라는 걸 만들어야 한다. DTO는 데이터 전송 객체(Data Transfer Object)를 의미한다. 클라이언트가 보내는 정보를 한정짓는 거라고 생각하면 편할 것 같다.
- DTO를 만들고 나서 적용하는 것까지 코드로 보자. 우선 movies 폴더에 dto폴더를 만들고 create-movie-dto 파일을 만든다.
// create-movie-dto
export class CreateMovieDto {
readonly title: string;
readonly year: number;
readonly genres: string[];
}
@Post()
create(@Body() movieData: CreateMovieDto) {
return this.moviesService.create(movieData);
}
그리고 컨트롤러와 서비스에 DTO 형식을 뿌려준다.(데이터 타입을 우리가 만든 DTO로 넣어주면 된다)
- DTO로 타입을 부여해줘도 여전히 다른 데이터가 들어왔을 때 데이터 검증 없이 그대로 보내준다. 그렇다면 DTO를 왜 쓰는 것일까? DTO는 우리가 프로그래머로서 코드를 더 간결하게 만들 수 있도록 해준다. 그리고 NestJS가 들어오는 쿼리에 대해 유효성을 검사할 수 있게 해준다.
- 이를 위해 우리는 main.ts에 파이프를 만들 것이다. 정확히는 유효성 검사용 파이프를 만들 것이다. 일반적으로 파이프는 미들웨어 같은 거라고 생각할 수 있다.
- app.useGlobalPipes를 사용해서 우리가 쓰고 싶은 파이프를 NestJS 어플리케이션에게 넘겨줄 것이다.
app.useGlobalPipes(new ValidationPipe());
우리가 하고 싶은 건 클래스의 유효성을 검사하는 것이기 때문에 class-validator와 class-transformer 두 개를 추가적으로 설치해줘야 한다.
- 이제는 DTO 파일에 유효성 검사를 해주는 데코레이터를 추가할 수 있다.(validationPipe를 넣어주고 class-validator를 설치해주었기 때문에)
import { IsNumber, IsString } from 'class-validator';
export class CreateMovieDto {
@IsString()
readonly title: string;
@IsNumber()
readonly year: number;
@IsString({ each: true })
readonly genres: string[];
}
- validationPipe 옵션
- whitelist: true로 설정하면 아무 decorator도 없는 어떠한 property의 object를 거른다. 따라서 설정한 값이 아닌 값이 들어오게 되면 validator에 도달하지도 못한다.
- forbidNonWhiteListed: true - 한 층 더 보안이 강화된 옵션으로 누군가 이상한 것을 보내면 리퀘스트 자체를 막아버릴 수 있다.
- transform: true - 유저들이 보낸 거를 우리가 원하는 실제 타입으로 변환해준다. 예를들어 param값을 가져올 때 url에서 가져오는 것이기 때문에 무조건 string으로 가져와지는데 db에 넣을 땐 number로 바꿔줘야 해서 불편하다. 이것을 param을 가져올 때 number타입으로 가져올 수 있다.
@Get(':id')
getOne(@Param('id') movieId: number): Movie {
return this.moviesService.getOne(movieId);
}
param 뒤에 가져오는 변수의 타입에 number라고 적으면 number라고 가져와지는 것이다.
따라서 타입을 바꿔줘야 하는 불편함을 없앨 수 있다.
- DTO 작성시 부분타입을 쓸 수도 있다. 사용하려면 npm install @nestjs/mapped-types를 설치해주어야한다. mapped-types는 타입을 변환시키고 사용할 수 있게 해주는 패키지이다.(nestjs 만든사람들이 만든 패키지)
- 부분 타입은 베이스 타입이 필요하다.
export class UpdateMovieDto extends PartialType(CreateMovieDto) {}
updateMovieDto를 이런 식으로 작성하게 되면 createDto와는 같되 전부 필수 사항이 아니라는 의미가 된다.
- app.module.ts에서 module을 import해오면 controll와 provider를 안적어도 연결이 된다.
- 모든 폴더의 모듈 부분에서 controller와 provider가 있는데 여기서 Nest가 자동으로 의존성 주입을 해주는 것이다.
- nest는 express 기반으로 돌아가기 때문에 res, req를 사용할 수 있다. 사용 방법은
와 같이 사용할 수 있다. 그런데 이렇게 사용하는 것은 좋은 방법은 아니다.@Get() getAll(@Req req, @Res res): Movie[] { res.json() return this.moviesService.getAll(); }
'📁Dev Log > TIL' 카테고리의 다른 글
2024_01_03 TIL (1) | 2024.01.04 |
---|---|
2024_01_02 TIL (1) | 2024.01.03 |
2023_12_28 TIL (1) | 2023.12.29 |
2023_12_27 TIL (2) | 2023.12.28 |
2023_12_26 TIL (2) | 2023.12.27 |