NestJS - CRUD / TypeORM 본문
미래의 나에게
검증을 하기 위한 두 라이브러리
yarn add class-validator
yarn add class-transformer
오늘은 오늘 과제였던 CRU API 구현 / TypeORM 에 대해 리뷰한다.
DTO / interface
먼저 본격적인 API 구현에 앞서, 이전과 같이 dto 와 Interface 를 작성한다.
( create 를 위한 dto / update 를 위한 dto)
워낙 불려다니는 곳이 많은 친구들이라 먼저 작성해두는 것이 마음 편하다.
// dto/create-product.input.ts
import { Field, InputType, Int } from '@nestjs/graphql';
import { Min } from 'class-validator';
@InputType() // 입력을 할 때 필요하기에 InputType
export class CreateProductInput {
@Field(() => String)
product_name: string;
@Min(0)
@Field(() => Int)
product_price: number;
@Min(0)
@Field(() => Int)
product_weight: number;
@Min(0)
@Field(() => Int)
product_kcal: number;
@Min(0)
@Field(() => Int)
product_protein: number;
@Min(0)
@Field(() => Int)
product_fat: number;
@Min(0)
@Field(() => Int)
product_mg: number;
@Min(0)
@Field(() => Int)
product_sugar: number;
}
// Field 값에는 Boolean / Float(소수점) / Date 가 들어갈 수 있다.
해당 dto 는 이전 entity에 작성한 column 값을 따르며
@ 데코레이터를 받는 Min 은 검증을 하기 위한 요소다. ( 최솟값이 0 을 의미한다.)
검증을 하기 위한 라이브러리는 위에 작성해 두었다.
// dto/update-product.input.ts
import { InputType, PartialType } from '@nestjs/graphql';
import { CreateProductInput } from './create-product.input';
@InputType()
export class UpdateProductInput extends PartialType(CreateProductInput) {}
update 를 위한 dto 는 create 를 위해 작성한 dto 를 재활용한다.
유틸리티인 PartialType 을 사용해 빈 값이라도 적용될 수 있도록 한다.
** interface 역시 create dto / update dto 를 활용하여 작성한다.
API
API 를 위한 초기 설정
// module.ts
@Module({
imports: [TypeOrmModule.forFeature([Product])], // entity를 전달하기 위한 메서드
// import 하지 않을 시, DB 연결이 되지 않는다
providers: [
ProductsResolver, // DI
ProductsService,
],
})
export class ProductsModule {}
// service.ts
@Injectable() // DI
export class ProductsService {
constructor(
@InjectRepository(Product) // DB에 저장하기 위해 entity 'Product'에 레파지토리 주입
private readonly productsRepository: Repository<Product>,
) {}
Create API
// create
// resolver.ts
@Mutation(() => Product)
createProduct(
@Args('createProductInput') createProductInput: CreateProductInput,
// dto 를 가져와, createProductInput 에 만들 객체 타입을 설정해 줌
): Promise<Product> { // 이때 DB에 저장후 return 하는 과정이 있음으로 Promise 사용
// Product 는 '엔티티'
return this.productsService.create({ createProductInput });
// service 에서 반환된 값을 module로 반환
}
// service.ts
create({ createProductInput }: IProductsServiceCreate): Promise<Product> {
// 인터페이스 & 위와 같음
const result = this.productsRepository.save({
...createProductInput, // // result 를 DB에 저장
});
return result;
}
Read API
// resolver.ts
// findAll
@Query(() => [Product]) // 여러 개이므로 대괄호로 표현
fetchProducts(): Promise<Product[]> {
return this.productsService.findAll(); // service 에서 받은 값 module로 반환
}
// findOne
@Query(() => Product)
fetchProduct(
@Args('productId') productId: string, // fetch 를 하기 위한 id 값을 아규먼트로 받음
): Promise<Product> {
return this.productsService.findOne({ productId });
// id 를 service로 전달 후 받은 값 module 에 반환
}
// service.ts
findAll(): Promise<Product[]> {
return this.productsRepository.find();
// TypeORM find() 사용 / 해당되는 전체값 fetchProducts 에 전달
}
// findOne
findOne({ productId }: IProductServiceFindOne): Promise<Product> {
return this.productsRepository.findOne({ where: { id: productId } });
// findOne 으로 조건식 'where'에 있는 값 리턴 후 fetchProduct 에 전달
}
Update API
// resolver.ts
@Mutation(() => Product)
updateProduct(
@Args('productId') productId: string, //
@Args('updateProductInput') updateProductInput: UpdateProductInput,
): Promise<Product> {
return this.productsService.update({ productId, updateProductInput });
}
// service.ts
async update({
productId, // update 를 위한 id
updateProductInput, // update 를 위한 변경된 값
}: IProductServiceUpdate): Promise<Product> {
const product = await this.findOne({ productId });
// async & await / findOne 이 이뤄질 때까지 기다림
this.checkUpdate({ product });
const result = this.productsRepository.save({
...product,
...updateProductInput, // 객체의 경우, 나중 값이 기존 값을 덮어씌움
});
return result;
}
// 검증
checkUpdate({ product }: IProductServiceCheckUpdate): void {
// if ('검증할 조건 아직 없음') {
// throw new UnprocessableEntityException('판매 불가 상품입니다.');
// } 검증할 것이 없으므로 주석 처리
console.log(product, '검증 완료되었습니다.');
}
Delete API
// resolver.ts
@Mutation(() => Boolean)
deleteProduct(@Args('productId') productId: string): Promise<boolean> {
return this.productsService.delete({ productId });
}
// service.ts
async delete({ productId }: IProductServiceDelete): Promise<boolean> {
const result = await this.productsRepository.softDelete({ id: productId });
return result.affected ? true : false; // 삭제한 데이터를 반환할 수 없기에
// affected 를 통해 동작했는지를 boolean 값으로 반환
}
}
interface IProductServiceDelete {
productId: string;
}
// entity.ts
@DeleteDateColumn()
deleteAt: Date; // 삭제 여부를 콜론으로 저장해 등록
주의 깊게 봐야 할 것은 softDelete.
softDelete 는 TypeORM 에서 제공하는 기능으로 DB 데이터의 삭제를 돕는 역할을 한다.
중요한 것은 DB에서 완전히 삭제되는 것이 아니라, '삭제된 척'을 하는 것.
이때, entity에서 softDelete와 함께 사용할 수 있는 @DeleteDateColumn 을 사용하여 삭제 시간을 기록할 수 있으며,
해당 DB 값이 null 이 아닌 경우, 보여주지 않게 된다.
softDelete & softRemove
- softDelete : 여러 ID 한번에 지우기 불가능 / 다른 칼럼으로도 삭제 가능
- softRemove : 여러 ID 한번에 지우기 가능 / 아이디로만으로도 삭제 가능
예외처리
// main.ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { HttpExceptionFilter } from './commons/filter/http-exception.filter';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalPipes(new ValidationPipe()); // 검증을 위한 Pipe
app.useGlobalFilters(new HttpExceptionFilter()); // 예외 처리를 위한 Filter
await app.listen(3000);
}
bootstrap();
// filter / http-exception.filter.ts
import {
Catch,
ExceptionFilter,
HttpException,
} from '@nestjs/common';
@Catch(HttpException) // Catch(httpException) 으로 예외처리 가능
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: HttpException) {
const status = exception.getStatus(); // 예외 시 상태 메시지
const message = exception.message; // 예외 내용
console.log('===================================');
console.log('예외가 발생했습니다.');
console.log('예외 내용 :', message);
console.log('예외 코드 :', status);
console.log('===================================');
}
}
작성한 뒤 playground 에서 확인한 모습
create
read
update
delete
'개발 > nest.js' 카테고리의 다른 글
Nest JS - 회원 API 구현 (hashing & ConflictException) (0) | 2023.02.10 |
---|---|
NestJs - Join API 구현 (0) | 2023.02.09 |
entity 작성 / OneToOne || OneToMany & ManyToOne || ManyToMany / 관계도 MySQL 적용 (0) | 2023.02.04 |
집계 & 정렬 & 서브 쿼리 (0) | 2023.02.04 |
nestJS - MySQL과의 연동 (0) | 2023.02.01 |