2022.07.25 - [IT] - [Nomad Corders] NestJS로 API 만들기 #2.0 to #2.3 정리
[Nomad Corders] NestJS로 API 만들기 #2.0 to #2.3 정리
#2 REST API #2.0 Movies Controller #2.1 More Routes #2.2 Movies Service part One #2.3 Movies Service part Two #2.4 DTOs and Validation part One #2.5 DTOs and Validation part Two #2.6 Modules and Dep..
ella-devblog.tistory.com
#2 REST API
#2.0 Movies Controller
#2.1 More Routes
#2.2 Movies Service part One
#2.3 Movies Service part Two
#2.4 DTOs and Validation part One
#2.5 DTOs and Validation part Two
#2.6 Modules and Dependency Injection
#2.7 Express on NestJS
#2.4 DTOs and Validation part One
DTO : Data Transfer Object 데이터 전송 객체
dto 폴더 생성 후 create.movie.dto.ts 파일 생성
export class CreateMovieDto {
readonly title: string;
readonly year: number;
readonly genres: string[];
}
사람들이 보냈으면 하는 것의 타입을 정함.
그래서 create 생성할 때 @Body로 받아오는 데이터의 타입을 CreateMovieDto로 정해줌
@Post()
create(@Body() movieData: CreateMovieDto) {
return this.moviesService.create(movieData);
}
그리고 main.ts 에서 유효성 검사용 pipe를 생성해준다.
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
//pipe 생성
app.useGlobalPipes(new ValidationPipe);
await app.listen(3000); //3000번 포트 리스닝
}
bootstrap();
이를 실행하기 위해선 아래에서 두 가지 설치가 필요함.
[>npm I class-validator class-transformer]
그런 다음 dto 파일을 다음과 같이 업데이트 하고,
import {IsString, IsNumber} from 'class-validator';
export class CreateMovieDto {
@IsString()
readonly title: string;
@IsNumber()
readonly year: number;
@IsString({each: true})
readonly genres: string[];
}
POST 해주면, 타입이 안맞다고 에러메세지를 띄워줌.
//pipe 생성
app.useGlobalPipes(new ValidationPipe({
//options
whitelist: true,
forbidNonWhitelisted: true,
}));
파이프 옵션에 위와같이 추가해주면
property가 존재하는지 안하는지 여부도 알려줌.
//pipe 생성
app.useGlobalPipes(new ValidationPipe({
//options
whitelist: true,
forbidNonWhitelisted: true,
//transform : 원래 param or body에서 받는 거는 string인데,
//true로 하면 실제 원하는 값으로 변경해줌.
transform: true,
}));
transform 옵션을 사용하면 기존에 @Param에서 string으로 받아왔던 id를
자동으로 number로 받아오기 때문에 형 변환이 필요 없음.
main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
//pipe 생성
app.useGlobalPipes(new ValidationPipe({
//options
whitelist: true,
forbidNonWhitelisted: true,
//transform : 원래 param or body에서 받는 거는 string인데,
//true로 하면 실제 원하는 값으로 변경해줌.
transform: true,
}));
await app.listen(3000); //3000번 포트 리스닝
}
bootstrap();
movie.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { Movie } from './entities/movie.entity';
import { CreateMovieDto } from './dto/create.movie.dto';
@Injectable()
export class MoviesService {
private movies: Movie[] = [];
getAll() : Movie[] {
return this.movies;
}
getOne(id: number): Movie {
//return this.movies.find(movie => movie.id === parseInt(id));
const movie = this.movies.find(movie => movie.id === id); //이렇게 해도 int로 변경 가능
if (!movie) {
throw new NotFoundException(`Movie with ID ${id} not found.`);
}
return movie;
}
deleteOne(id: number) {
this.getOne(id);
this.movies = this.movies.filter(movie => movie.id !== id);
}
create(movieData: CreateMovieDto) {
this.movies.push({
id: this.movies.length + 1,
...movieData,
})
}
update(id: number, updateData) {
const movie = this.getOne(id);
this.deleteOne(id);
this.movies.push({...movie, ...updateData});
}
}
movies.controller.ts
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common';
import { Movie } from './entities/movie.entity';
import { MoviesService } from './movies.service';
import { CreateMovieDto } from './dto/create.movie.dto';
@Controller('movies') //localhost:3000/movies
export class MoviesController {
//Service에 접근하는 법
constructor(private readonly moviesService: MoviesService) {}
@Get()
getAll(): Movie[] {
return this.moviesService.getAll();
}
@Get(":id") //localhost:3000/movies/id
getOne(@Param('id') movieID: number): Movie {
return this.moviesService.getOne(movieID);
}
@Post()
create(@Body() movieData: CreateMovieDto) {
return this.moviesService.create(movieData);
}
@Delete(":id")
remove(@Param('id') movieID: number) {
return this.moviesService.deleteOne(movieID);
}
@Patch(":id")
patch(@Param('id') movieID: number, @Body() updateData) {
return this.moviesService.update(movieID, updateData);
}
}
#2.5 DTOs and Validation part Two
저번 영상에선 create dto를 만들었다면, 이번엔 update dto를 만들어 보자.
- mapped-types : 타입을 변환시키고 사용할 수 있게 하는 패키지
일단 PartialType을 쓰기위해 @nestjs/mapped-types 를 설치해준다.
=> 나는 이걸로 설치 안돼서 @nestjs/swagger 로 설치함.
@nestjs/graphql 로도 사용 가능.
그리고 [>npm I @nestjs/swagger] 설치했더니 버전 충돌 에러나서
[>npm I --force @nestjs/swagger]로 강제설치 해줬다.
update-movie.dto.ts
import {IsString, IsNumber} from 'class-validator';
import {PartialType} from '@nestjs/swagger';
import {CreateMovieDto} from './create-movie.dto';
export class UpdateMovieDto extends PartialType(CreateMovieDto) { }
요렇게 dto 생성해줌.
- class-validator : 거의 모든 것의 유효성을 검사할 수 있음.
create-movie.dto.ts
import {IsString, IsNumber, IsOptional} from 'class-validator';
export class CreateMovieDto {
@IsString()
readonly title: string;
@IsNumber()
readonly year: number;
@IsOptional()
@IsString({each: true})
readonly genres: string[];
}
movie.controller.ts
import { Body, Controller, Delete, Get, Param, Patch, Post, Put, Query } from '@nestjs/common';
import { Movie } from './entities/movie.entity';
import { MoviesService } from './movies.service';
import { CreateMovieDto } from './dto/create-movie.dto';
import { UpdateMovieDto } from './dto/update-movie.dto';
@Controller('movies') //localhost:3000/movies
export class MoviesController {
//Service에 접근하는 법
constructor(private readonly moviesService: MoviesService) {}
@Get()
getAll(): Movie[] {
return this.moviesService.getAll();
}
@Get(":id") //localhost:3000/movies/id
getOne(@Param('id') movieID: number): Movie {
return this.moviesService.getOne(movieID);
}
@Post()
create(@Body() movieData: CreateMovieDto) {
return this.moviesService.create(movieData);
}
@Delete(":id")
remove(@Param('id') movieID: number) {
return this.moviesService.deleteOne(movieID);
}
@Patch(":id")
patch(@Param('id') movieID: number, @Body() updateData: UpdateMovieDto) {
return this.moviesService.update(movieID, updateData);
}
}
movie.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { Movie } from './entities/movie.entity';
import { CreateMovieDto } from './dto/create-movie.dto';
import { UpdateMovieDto } from './dto/update-movie.dto';
@Injectable()
export class MoviesService {
private movies: Movie[] = [];
getAll() : Movie[] {
return this.movies;
}
getOne(id: number): Movie {
//return this.movies.find(movie => movie.id === parseInt(id));
const movie = this.movies.find(movie => movie.id === id); //이렇게 해도 int로 변경 가능
if (!movie) {
throw new NotFoundException(`Movie with ID ${id} not found.`);
}
return movie;
}
deleteOne(id: number) {
this.getOne(id);
this.movies = this.movies.filter(movie => movie.id !== id);
}
create(movieData: CreateMovieDto) {
this.movies.push({
id: this.movies.length + 1,
...movieData,
})
}
update(id: number, updateData: UpdateMovieDto) {
const movie = this.getOne(id);
this.deleteOne(id);
this.movies.push({...movie, ...updateData});
}
}
#2.6 Modules and Dependency Injection
우리가 만든 api를 좀 더 좋은 구조로 만들어 보자.
app.module은 AppService랑 AppController만 가지고있어야 함. (아래에서 설명)
[>nest g mo] = [>nest generate module]
모듈을 하나 만들어 주면, app.module.ts 에 자동으로 모듈이 생성, right?
app.module.ts에 있는 MoviesController, MoviesService를 삭제하고,
movies.module.ts에 controllers랑 providers를 등록해줌.
그리고,
[>nest g co] name? app
AppController를 하나 새로 만들어 주고, 다음과 같이 작성.
app.controller.ts
import { Controller, Get } from '@nestjs/common';
@Controller('')
export class AppController {
@Get()
home() {
return "Welcome to my Movie API";
}
}
app.module.ts
import { Module } from '@nestjs/common';
import { MoviesModule } from './movies/movies.module';
import { AppController } from './movies/app.controller';
//@(Decorator) : 클래스에 함수 기능 추가
@Module({
imports: [MoviesModule],
controllers: [AppController],
providers: [],
})
export class AppModule {}
movies.module.ts
import { Module } from '@nestjs/common';
import { MoviesController } from './movies.controller';
import { MoviesService } from './movies.service';
@Module({
controllers: [MoviesController],
providers: [MoviesService],
})
export class MoviesModule {}
=> 실행해보면 다음과 같음. 결국, AppController는 홈페이지 메인 역할을 하는 것.
- Dependency Injection
#2.7 Express on NestJS
앞에서 언급했듯이 NestJS는 Express 위에서 돌아감.
그래서 Express 객체인 @Req(), @Res()를 사용해서 접근할 수 있음. But not 추천.
Why?
nestjs는 express와 fastify 둘 다 사용가능하고 서로 전환가능 함.
(fastify는 express보다 2배 정도 빠름.)
이 두 개의 프레임워크에서 동시에 돌아가는 데, nestjs만 쓰면 두 개 전환은 알아서 해줌.
근데 Express 객체를 쓰면 느리고 효율이 떨어짐.
그래서 nestjs에서 req, res 객체를 많이 사용하는 것은 안좋음.
So, nestjs에서는 어떻게 사용되는지 잘 이해해야 함.
2022.07.26 - [IT] - [Nomad Corders] NestJS로 API 만들기 #3 정리
[Nomad Corders] NestJS로 API 만들기 #3 정리
2022.07.25 - [IT] - [Nomad Corders] NestJS로 API 만들기 #2.4 to #2.7 정리 [Nomad Corders] NestJS로 API 만들기 #2.4 to #2.7 정리 2022.07.25 - [IT] - [Nomad Corders] NestJS로 API 만들기 #2.0 to #2.3..
ella-devblog.tistory.com
'IT > DEV Study' 카테고리의 다른 글
[Nomad Corders] NestJS로 API 만들기 #4 정리 (완강) (1) | 2022.07.27 |
---|---|
[Nomad Corders] NestJS로 API 만들기 #3 정리 (358) | 2022.07.26 |
[Nomad Corders] NestJS로 API 만들기 #2.0 to #2.3 정리 (409) | 2022.07.25 |
[Nomad Corders] NestJS로 API 만들기 #0, #1 정리 (2) | 2022.07.25 |
[TypeScript] 작은 따옴표(')와 백틱(`)을 구분 못한 바보의 한탄 TT (421) | 2022.07.11 |