1.[nest-streaming:feat] Follow 기능 생성 & 삭제 완성
2.[nest-streaming:convention] Error Handling 하는 Exception
https://github.com/node9137/nest-streaming/pull/8
비즈니스 로직 단에서 발생하는 커스텀 에러 구조 와 커스텀 에러 처리 및 전역적 발생 에러 처리 를 위한 고민
=> Class 단위 Custom Error 와 nest 에서 제공하는 Filter 단위 구현
export interface CommonBaseError {
status: false;
businessCode: number;
message: string;
}
export interface NotExistedUser extends CommonBaseError{
status : false;
businessCode : 1002,
message:"정보에 일치하는 회원이 없습니다."
}
------------------------------------------------
if(isExistedUser)
throw typia.random<AlreadyExistedUser>();
{ "statusCode": 500, "message": "Internal server error" }
=> HTTP exception 및 상속된 Class 가 아닌 경우 기본 JSON Response
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
export declare class HttpException extends Error {
constructor(response: string | Record<string, any>, status: number, options?: HttpExceptionOptions);
}
=> options 에는 cause(발생 원인) 와 description (설명) 존재
export abstract class ExceptionBase extends Error {
abstract code: string;
public readonly correlationId: string;
constructor(
readonly message: string,
readonly businessCode:number,
readonly cause?: Error
) {
super(message);
Error.captureStackTrace(this, this.constructor);
}
toJSON(): SerializedException {
return {
status:false,
message: this.message,
code: this.code,
businessCode : this.businessCode,
cause : this.cause !== undefined ? JSON.stringify(this.cause) : undefined,
};
}
}
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
BadRequestException,
} from "@nestjs/common";
import { HttpAdapterHost } from "@nestjs/core";
import { BadRequestException as BadRequest } from "src/errors/bad-request-error";
import { DatabaseFailedException } from "src/errors/database-failed.error";
import { ExceptionBase } from "src/errors/exception.base";
import { ServerFailedException } from "src/errors/server-failed.error";
import { TypeORMError } from "typeorm";
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
static readonly dbErrorMessage = "데이터베이스 오류입니다.";
constructor(
private readonly httpAdapterHost: HttpAdapterHost,
) {}
catch(exception: unknown, host: ArgumentsHost): void {
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
if(exception instanceof ExceptionBase){
const data = exception.toJSON();
httpAdapter.reply(ctx.getResponse(),data,400);
}
else if(exception instanceof BadRequestException){
httpAdapter.reply(ctx.getResponse(),new BadRequest(),400)
}
else if(exception instanceof TypeORMError){
httpAdapter.reply(ctx.getResponse(),new DatabaseFailedException,400);
}
else if(exception instanceof HttpException){
const status = exception.getStatus();
httpAdapter.reply(ctx.getResponse(),exception,status);
}
else{
httpAdapter.reply(ctx.getResponse(),new ServerFailedException,500);
}
}
}
=> 서버가 의도치 않은 부분에서 중단 및 종료 방지 + Logging 이나 다른 방향으로 Handling 가능
export class NotExistedUser extends ExceptionBase{
static readonly message = "정보에 일치하는 회원이 없습니다."
public readonly code = 'USER.NOT_EXIST'
static readonly businessCode = 1002
constructor(cause?:Error,metadata?:unknown){
super(NotExistedUser.message,NotExistedUser.businessCode,cause)
}
}
=>
{
"status": false,
"message": "정보에 일치하는 회원이 없습니다.",
"code": "USER.NOT_EXIST",
"businessCode": 1002
}
response 는 toJSON 을 통해 JSON 화
error.stack 출력시 , 이렇게 발생 경로 출력
( 이때 주의해야 할 점은 이 stack 은 string type)
@Module({
imports: [ConfigModule.forRoot({}), AlarmModule, TestMoudle],
controllers: [],
providers: [
{
provide:APP_FILTER,
useClass:BadRequestExceptionFilter
},
{
provide:APP_FILTER,
useClass:AllExceptionsFilter
},
],
})
app.module.ts
@Module({
imports: [AlarmModule],
providers: [
{
provide:APP_FILTER,
useClass:AllExceptionsFilter
},
],
})
alram.module.ts
@Module({
imports: [AlarmModule],
providers: [
{
provide:APP_FILTER,
useClass:BadRequestExceptionFilter
},
],
})
https://github.com/Sairyss/domain-driven-hexagon/blob/master/src/libs/application/interceptors/exception.interceptor.ts
https://docs.nestjs.com/exception-filters