icon

예외 처리와 필터


 NestJS는 강력하고 유연한 예외 처리 메커니즘을 제공합니다.

 이를 통해 애플리케이션의 안정성을 높이고 예외 상황에 대한 일관된 응답을 보장할 수 있습니다.

NestJS의 예외 처리 메커니즘

 NestJS는 예외가 발생하면 내장된 전역 예외 필터를 사용하여 적절한 응답을 생성합니다.

 이 과정은 다음과 같습니다.

  1. 예외 발생
  2. 예외 필터가 예외를 캐치
  3. 적절한 응답 형식으로 변환
  4. 클라이언트에 응답 전송

HttpException 사용

 NestJS는 HttpException 클래스를 제공하여 HTTP 예외를 쉽게 처리할 수 있게 합니다.

@Get()
findAll() {
  throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}

 이 코드는 403 Forbidden 응답을 생성합니다.

커스텀 예외 클래스

 애플리케이션 특화된 예외를 처리하기 위해 커스텀 예외 클래스를 생성할 수 있습니다.

export class UserNotFoundException extends HttpException {
  constructor(userId: string) {
    super(`User with id ${userId} not found`, HttpStatus.NOT_FOUND);
  }
}
 
@Get(':id')
findOne(@Param('id') id: string) {
  const user = this.userService.findOne(id);
  if (!user) {
    throw new UserNotFoundException(id);
  }
  return user;
}

예외 필터 구현

 예외 필터를 사용하여 예외 처리 로직을 커스터마이즈할 수 있습니다.

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse<Response>();
    const request = ctx.getRequest<Request>();
    const status = exception.getStatus();
 
    response
      .status(status)
      .json({
        statusCode: status,
        timestamp: new Date().toISOString(),
        path: request.url,
        message: exception.message,
      });
  }
}

 이 필터는 HttpException을 캐치하여 커스텀 응답 형식을 생성합니다.

전역 예외 필터 vs 특정 필터

 전역 예외 필터

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalFilters(new HttpExceptionFilter());
  await app.listen(3000);
}
bootstrap();

 특정 컨트롤러/라우트에 대한 필터

@Controller('cats')
@UseFilters(new HttpExceptionFilter())
export class CatsController {}

 전역 필터는 모든 예외에 대해 일관된 처리를 제공하는 반면, 특정 필터는 더 세밀한 제어가 필요한 경우에 유용합니다.

비동기 작업의 예외 처리

 NestJS는 비동기 작업의 예외도 자동으로 처리합니다.

@Get()
async findAll() {
  throw new HttpException('Async error', HttpStatus.INTERNAL_SERVER_ERROR);
}

 Observable을 사용하는 경우

@Get()
findAll(): Observable<any> {
  return throwError(() => new HttpException('Observable error', HttpStatus.BAD_REQUEST));
}

다양한 예외 상황 처리

 데이터베이스 작업

@Injectable()
export class UserService {
  async create(createUserDto: CreateUserDto) {
    try {
      return await this.userRepository.create(createUserDto);
    } catch (error) {
      if (error.code === 'ER_DUP_ENTRY') {
        throw new ConflictException('User already exists');
      }
      throw new InternalServerErrorException();
    }
  }
}

 외부 API 호출

@Injectable()
export class ExternalService {
  async fetchData() {
    try {
      const response = await axios.get('https://api.example.com/data');
      return response.data;
    } catch (error) {
      if (error.response) {
        throw new HttpException(error.response.data, error.response.status);
      }
      throw new ServiceUnavailableException('External service is unavailable');
    }
  }
}

로깅과 모니터링을 위한 예외 처리

 효과적인 로깅과 모니터링은 애플리케이션의 안정성을 크게 향상시킵니다.

@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
  private readonly logger = new Logger(AllExceptionsFilter.name);
 
  catch(exception: unknown, host: ArgumentsHost) {
    const ctx = host.switchToHttp();
    const response = ctx.getResponse();
    const request = ctx.getRequest();
 
    const status = 
      exception instanceof HttpException
        ? exception.getStatus()
        : HttpStatus.INTERNAL_SERVER_ERROR;
 
    const message = 
      exception instanceof HttpException
        ? exception.message
        : 'Internal server error';
 
    this.logger.error(
      `${request.method} ${request.url}`,
      exception instanceof Error ? exception.stack : 'Unknown error'
    );
 
    response.status(status).json({
      statusCode: status,
      timestamp: new Date().toISOString(),
      path: request.url,
      message: message,
    });
  }
}

 이 필터는 모든 예외를 캐치하여 로깅하고, 적절한 응답을 생성합니다.

Best Practices와 주의사항

  1. 예외 계층 구조 : 애플리케이션의 도메인에 맞는 예외 계층 구조를 설계하세요.
  2. 의미 있는 예외 메시지 : 예외 메시지는 문제를 명확히 설명해야 합니다.
  3. 적절한 HTTP 상태 코드 사용 : 각 예외 상황에 맞는 HTTP 상태 코드를 사용하세요.
  4. 민감한 정보 노출 주의 : 스택 트레이스 등 민감한 정보가 클라이언트에게 노출되지 않도록 주의하세요.
  5. 비동기 작업 주의 : 비동기 작업의 예외 처리를 항상 고려하세요.
  6. 전역 필터 활용 : 일관된 예외 처리를 위해 전역 필터를 활용하세요.
  7. 로깅 전략 : 예외 발생 시 충분한 컨텍스트 정보를 로깅하세요.
  8. 테스트 : 예외 처리 로직에 대한 단위 테스트와 통합 테스트를 작성하세요.
  9. 성능 고려 : 예외 처리가 애플리케이션 성능에 미치는 영향을 고려하세요.
  10. 문서화 : 예외 처리 전략과 커스텀 예외 클래스를 문서화하세요.

 NestJS의 예외 처리와 필터 시스템은 견고하고 안정적인 애플리케이션을 구축하는 데 핵심적인 역할을 합니다.

 내장 HttpException과 커스텀 예외 클래스를 적절히 활용하면 애플리케이션의 다양한 오류 상황을 명확하고 일관되게 처리할 수 있습니다.

 예외 필터를 통해 예외 처리 로직을 세밀하게 제어할 수 있으며, 이는 특히 로깅, 모니터링, 그리고 클라이언트에 대한 응답 커스터마이징에 유용합니다. 전역 필터를 사용하여 애플리케이션 전체에 일관된 예외 처리를 적용할 수 있으며, 필요한 경우 특정 컨트롤러나 라우트에 대해 더 구체적인 예외 처리를 구현할 수 있습니다.

 비동기 작업, 데이터베이스 연산, 외부 API 호출 등 다양한 상황에서 발생할 수 있는 예외에 대비하는 것이 중요합니다.

 각 상황에 맞는 적절한 예외 처리 전략을 수립하고, 이를 통해 애플리케이션의 안정성과 신뢰성을 높일 수 있습니다.