-
NestJs TypeOrm Exception공부하기/node.js 2023. 2. 24. 20:09
소프트웨어를 개발하면서 예외처리는 필수 사항이다.
만약 예외 상황이 발생할 경우 프로그램이 강제로 종료될 수 있어 사용자에게 나쁜 경험을 제공하며 심각할 경우 데이터 손실을 초래할 수 있다.
예외 처리를 통해 발생한 오류 메시지를 적절하게 처리하면, 디버깅과 오류 추적이 용이해지고 오류가 발생한 원인과 위치를 파악할 수 있다.
또 예기치 않은 입력이나 상황에 대응할 수 있어 안정성을 높이고 예외 상황에 대한 처리 방법을 명시적으로 나타내 코드의 가독성을 높여준다.
목표
- 예측 가능한 에러 httpException 처리
- Http Response 값 체크 후 에러 처리
- query error 전부 핸들링
- 데이타베이스 에러는 전부 500 에러로 처리
- 개발자의 실수로 발생한 서버에러 핸들링
- 개발자의 실수로 발생하는 타입 에러 처리
예측 가능한 에러 httpException 처리
1. 추상클래스 ErrorController를 선언하고 공통 함수를 사용하는 컨트롤러에서 extends 받아서 validation 함수를 사용한다.
import { BadRequestException, InternalServerErrorException, } from '@nestjs/common'; import { QueryResult } from 'typeorm'; export abstract class ErrorController { isEmptyArray(arr: Array<object>, msg: string): void { // 파라메터가 배열 타입이 아닌 경우는 repository에 문제일 가능성이 매우 높다. if (!Array.isArray(arr)) throw new InternalServerErrorException(); if (arr.length === 0) throw new NotFoundException(msg); } isEmpty(obj: object, msg: string) { if (!obj) throw new NotFoundException(msg); } }
2. 상속받은 컨트롤러에서 결과값을 체크한 후 리턴한다.
... @Controller('survey') export class SurveyController extends ErrorController { constructor( ... ) { super(); } @Get('find') async findAll( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, @Query('size', new DefaultValuePipe(10), ParseIntPipe) size: number, @Query( 'sort', new DefaultValuePipe(SORT_OPTION.ASC), new ParseEnumPipe(SORT_OPTION), ) sort: SORT_OPTION, ) { const result = await this.findAllSurveyInPort.execute({ page, size, sort, }); this.isEmptyArray(result, '설문지가 존재하지 않습니다.'); return result; } @Get('find/:id') async findOne(@Param('id', ParseIntPipe) id: number) { const result = await this.findOneSurveyInPort.execute(id); this.isEmpty(result, `id: ${id} 설문지를 찾을 수 없습니다.`); return result; } @Get('/search') async search( @Query('page', new DefaultValuePipe(1), ParseIntPipe) page: number, @Query('size', new DefaultValuePipe(10), ParseIntPipe) size: number, @Query( 'sort', new DefaultValuePipe(SORT_OPTION.ASC), new ParseEnumPipe(SORT_OPTION), ) sort: SORT_OPTION, @Query('sort', new DefaultValuePipe('')) keyword: string, ) { const result = await this.searchSurveyInPort.execute({ keyword, page, size, sort, }); this.isEmptyArray(result, '설문지가 존재하지 않습니다.'); return result; } @Post('create') async create(@Body() surveyCreateDto: SurveyCreateDto) { const result = await this.createSurveyInPort.execute(surveyCreateDto); this.isEmpty(result, `설문지 생성에 실패했습니다.`); return result; } @Patch('update/:id') async update( @Body() surveyUpdateDto: SurveyUpdateDto, @Param('id', ParseIntPipe) id: number, ) { const result = await this.updateSurveyInPort.execute({ ...surveyUpdateDto, id, }); this.isEmpty(result, `id: ${id} 설문지를 찾을 수 없습니다.`); return result; } @Delete('delete/:id') async delete(@Param('id', ParseIntPipe) id: number) { const result = await this.deleteSurveyInPort.execute(id); return result; } }
테스트
[설문지 findone / DB에 저장된 id값 요청] { "id": 1, "created_at": "...", "updated_at": "...", "name": "...", "description": "..." } [설문지 findone / DB에 저장되지 않은 id값 요청] { "statusCode": 400, "message": "id: 13409583094 설문지를 찾을 수 없습니다.", "error": "Not Found" } [설문지 findall, search / 존재하는 page 요청] [ { "id": 1, "created_at": "...", "updated_at": "...", "name": "...", "description": "..." }, ... ] [설문지 findall, search / 존재하지 않는 page 요청] { "statusCode": 400, "message": "설문지가 존재하지 않습니다.", "error": "Not Found" }
- [설문지 findone / DB에 저장된 id값 요청] : 정상적으로 설문지 리턴
- [설문지 findone / DB에 저장되지 않은 id값 요청] : 의도한대로 NotFoundException 리턴
- [설문지 findall, search / 존재하지 않는 page 요청] : 의도한대로 NotFoundException 리턴
- [설문지 findall, search / 존재하는 page 요청] : 정상적으로 설문지 배열 리턴
개발자의 실수로 발생한 서버에러 핸들링
1. TypeExceptionFilter 생성
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, InternalServerErrorException, } from '@nestjs/common'; import { Request, Response } from 'express'; @Catch(TypeError) export class TypeExceptionFilter implements ExceptionFilter { catch(exception: Error, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); response.status(500).json({ statusCode: 500, message: '서버 에러', error: 'Internal Server Error', }); } }
2. main.ts -> 글로벌 필터 적용
... import { TypeExceptionFilter } from './common/filter/type-exception.filter'; async function bootstrap() { ... app.useGlobalFilters(new TypeExceptionFilter()); ... } bootstrap();
3. 의도적으로 타입 에러를 발생시켜 테스트 진행
@Get('find/:id') async findOne(@Param('id', ParseIntPipe) id: number) { // @ts-ignore console.log(null.test()); ... }
테스트
{ "statusCode": 500, "message": "서버 에러", "error": "Internal Server Error" }
-> 의도적으로 타입 에러를 발생시킨 엔드포인트에 접근할 시 TypeExceptionFilter 정상 동작
Query error 전부 핸들링
1. TypeOrmExceptionFilter 생성
import { ArgumentsHost, Catch, ExceptionFilter } from '@nestjs/common'; import { Request, Response } from 'express'; import { TypeORMError } from 'typeorm'; @Catch(TypeORMError) export class TypeOrmExceptionFilter implements ExceptionFilter { catch(exception: Error, host: ArgumentsHost) { const ctx = host.switchToHttp(); const response = ctx.getResponse<Response>(); const request = ctx.getRequest<Request>(); response.status(500).json({ statusCode: 500, message: exception.message, error: 'Internal Server Error', }); } }
2. main. ts -> TypeOrmExceptionFilter 글로벌 필터 적용
... async function bootstrap() { ... app.useGlobalFilters(new TypeOrmExceptionFilter(), new TypeExceptionFilter()); ... } bootstrap();
3. 의도적으로 타입 에러를 발생시켜 테스트 진행
자신이 사용하는 레파지토리중 테스트할 레파지토리를 선택한 후 일부러 데이터베이스 에러를 발생시킴 execute( params: SurveyFindOneOutPortInputDto, ): Promise<SurveyFindOneOutPortOutputDto> { return this.surveyRepo.findOne({ where: { id: params, //@ts-ignore 존재하지않는컬럼: 123, }, }); }
테스트
{ "statusCode": 500, "message": "Property \"존재하지않는컬럼\" was not found in \"Survey\". Make sure your query is correct.", "error": "Internal Server Error" }
-> 의도적으로 타입 에러를 발생시킨 엔드포인트에 접근할 시 TypeOrmExceptionFilter 정상 동작
NestJs Filter 탐색 결과
- 내가 의도한 특정 에러만 컨트롤 할 수 있다.
- 애플리케이션에서 발생하는 모든 에러를 일관된 타입으로 리턴할 수 있다.
- 런타임에서 예측할 수 없는 exception까지 컨트롤 할 수 있다.
'공부하기 > node.js' 카테고리의 다른 글
헥사고날 NestJs, TypeOrm dto type 리펙토링 (0) 2023.02.26 NestJs Controller @Query Pipe 기록 (0) 2023.02.25 NestJs TypeOrm 엔티티 공통 컬럼 관리 (0) 2023.02.24 NestJs TypeOrm MySql Entity Name Error (0) 2023.02.24 MySql [ Like, Full Text Index ] 테스트 (0) 2023.02.24 - 예측 가능한 에러 httpException 처리