예외 처리와 필터
NestJS는 강력하고 유연한 예외 처리 메커니즘을 제공합니다.
이를 통해 애플리케이션의 안정성을 높이고 예외 상황에 대한 일관된 응답을 보장할 수 있습니다.
NestJS의 예외 처리 메커니즘
NestJS는 예외가 발생하면 내장된 전역 예외 필터를 사용하여 적절한 응답을 생성합니다.
이 과정은 다음과 같습니다.
- 예외 발생
- 예외 필터가 예외를 캐치
- 적절한 응답 형식으로 변환
- 클라이언트에 응답 전송
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와 주의사항
- 예외 계층 구조 : 애플리케이션의 도메인에 맞는 예외 계층 구조를 설계하세요.
- 의미 있는 예외 메시지 : 예외 메시지는 문제를 명확히 설명해야 합니다.
- 적절한 HTTP 상태 코드 사용 : 각 예외 상황에 맞는 HTTP 상태 코드를 사용하세요.
- 민감한 정보 노출 주의 : 스택 트레이스 등 민감한 정보가 클라이언트에게 노출되지 않도록 주의하세요.
- 비동기 작업 주의 : 비동기 작업의 예외 처리를 항상 고려하세요.
- 전역 필터 활용 : 일관된 예외 처리를 위해 전역 필터를 활용하세요.
- 로깅 전략 : 예외 발생 시 충분한 컨텍스트 정보를 로깅하세요.
- 테스트 : 예외 처리 로직에 대한 단위 테스트와 통합 테스트를 작성하세요.
- 성능 고려 : 예외 처리가 애플리케이션 성능에 미치는 영향을 고려하세요.
- 문서화 : 예외 처리 전략과 커스텀 예외 클래스를 문서화하세요.
NestJS의 예외 처리와 필터 시스템은 견고하고 안정적인 애플리케이션을 구축하는 데 핵심적인 역할을 합니다.
내장 HttpException
과 커스텀 예외 클래스를 적절히 활용하면 애플리케이션의 다양한 오류 상황을 명확하고 일관되게 처리할 수 있습니다.
예외 필터를 통해 예외 처리 로직을 세밀하게 제어할 수 있으며, 이는 특히 로깅, 모니터링, 그리고 클라이언트에 대한 응답 커스터마이징에 유용합니다. 전역 필터를 사용하여 애플리케이션 전체에 일관된 예외 처리를 적용할 수 있으며, 필요한 경우 특정 컨트롤러나 라우트에 대해 더 구체적인 예외 처리를 구현할 수 있습니다.
비동기 작업, 데이터베이스 연산, 외부 API 호출 등 다양한 상황에서 발생할 수 있는 예외에 대비하는 것이 중요합니다.
각 상황에 맞는 적절한 예외 처리 전략을 수립하고, 이를 통해 애플리케이션의 안정성과 신뢰성을 높일 수 있습니다.