๐ฅ 07. ์๋ฌ ์ฒ๋ฆฌ โ ๊ณ ๊ฐ์๊ฒ ์น์ ํ '๋ถ๋ง ์ฒ๋ฆฌ ์ ๋ด๋ฐ' ๋ง๋ค๊ธฐ
๐ ๊ฐ์
์๋ฒ๊ฐ ๋ป์ง ์๋๋ก ์๋ฐฉํ๊ณ , ํด๋ผ์ด์ธํธ์๊ฒ ํญ์ ์น์ ํ๊ณ ์ผ๊ด๋ ์๋ฌ ์๋ต์ ๋ด๋ ค์ฃผ๋ ์ ์ญ ์์ธ ํํฐ(Exception Filter)๋ฅผ ๋ง์คํฐํฉ๋๋ค.
๐ ๋ชฉ์ฐจ
- ๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
- ๐ค ์ ์์์ผ ํ๋๊ฐ
- ๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
- ๐งฉ ๋ด์ฅ ์์ธ์ ์ปค์คํ ์์ธ ๐ก
- ๐งฉ ์ ์ญ ์์ธ ํํฐ (Global Exception Filter) ๐ข
- ๐งช ๋ฐ๋ผํด๋ณด๊ธฐ: ๊ถ๊ทน์ ๊ธ๋ก๋ฒ ํํฐ ๊น์๋ณด๊ธฐ
- ๐ผ ๋ฒ ์คํธ ํ๋ํฐ์ค์ ์ค๋ฌด ํ
- ๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
- ๐๏ธ ์นํธ์ํธ
- ๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
๐ ์ด ๋ฌธ์๋ฅผ ์ฝ๊ธฐ ์ ์
โฑ๏ธ ์์ ์ฝ๊ธฐ ์๊ฐ: 15๋ถ
๐งณ ์ ์ ์ง์ ์ฒดํฌ๋ฆฌ์คํธ
- HTTP ์ํ ์ฝ๋(400, 401, 404, 500 ๋ฑ)์ ๋๋ต์ ์ธ ์๋ฏธ๋ฅผ ์๊ณ ์๋ค.
- JavaScript์
try-catch๋ฌธ์ ์ญํ ๊ณผthrow new Error()๋ฌธ๋ฒ์ ์ดํดํ๋ค.
๐บ๏ธ ์ด ๋ฌธ์์ ํ๋ฆ
NestJS ๋ด์ฅ ์๋ฌ ๋ฑ๊ธฐ โ ๋๋ง์ ๋น์ฆ๋์ค ์๋ฌ๋ก ํฌ์ฅํ๊ธฐ โ ์๋ฒ ํญ๋ฐ์ ๋ง๋ ์ ์ญ ๊ทธ๋ฌผ๋ง(Filter) ๋์ง๊ธฐ โ ์ค๋ฌด ๋ก๊น
๋ฐ ์๋ฌ ์ฝ๋ ๊ด๋ฆฌ๋ฒ
๐ฏ ์ด ๋ฌธ์๋ฅผ ๋ค ์ฝ์ผ๋ฉด ํ ์ ์๋ ๊ฒ
- ์ปจํธ๋กค๋ฌ์์ ์ง์ ๋ถํ
try-catch๋๋ฐฐ๋ฅผ ๊ฑท์ด๋ด๊ณ ์ฝ๋ ๊ฐ๋ ์ฑ์ 2๋ฐฐ ๋์ผ ์ ์๋ค. - ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๊ฐ ํ์ฑํ๊ธฐ ์ฌ์ด "์ผ๊ด๋ JSON ์๋ฌ ์๋ต ํฌ๋งท"์ ๊ท๊ฒฉํํ ์ ์๋ค.
- ์ ์ ์๋ ์น๋ช ์ 500 ์๋ฌ ๋ฐ์ ์ ์ง์ ๋ ์ฑ๋(Slack ๋ฑ)๋ก ์ฆ์ ์๋ฆผ์ ์๋ ๊ตฌ์กฐ๋ฅผ ์งค ์ ์๋ค.
๐ค ์ ์์์ผ ํ๋๊ฐ
์ด๋ณด ์์ ๋ฐฑ์๋ ์ฝ๋์ ๊ฐ์ฅ ํฐ ํน์ง์ ๋ผ์ฐํฐ๋ง๋ค try { ... } catch (e) { res.status(500).send(e) } ๊ฐ ๋๋ฐฐ๋์ด ์๋ค๋ ๊ฑฐ์ผ.
์ด๋ ๊ฒ ์ง๋ฉด ํ๋ก ํธ์๋ ์
์ฅ์์๋ ์ด๋ค API๋ ์๋ฌ๋ฅผ ํ
์คํธ๋ก ์ฃผ๊ณ , ์ด๋ค API๋ JSON์ผ๋ก ์ฃผ๊ณ , ์๋ต ํํ๊ฐ ์ ๋ฉ๋๋ก๋ผ ์๋ฌ ํ์
์ ๋์ฐ๋ ๊ฒ ์์ฒด๊ฐ ๊ณ ํต์ด ๋ผ.
NestJS์ ์์ธ ํํฐ(Exception Filter) ๋ฅผ ๋ฐฐ์ฐ๋ฉด, ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ํ ๊ณณ์ผ๋ก ์น ๋ชฐ์์(์ค์ ์ง์คํ) ์ฐ์ํ๊ฒ ํ๋ก ํธ์๋์๊ฒ ์ผ๊ด๋ ์์์ ๋๊ฒจ์ค ์ ์์ด.
๐๏ธ ๋น์ ๋ก ๋จผ์ ์ดํดํ๊ธฐ
๐ง 5์ด์๊ฒ ์ค๋ช ํ๋ค๋ฉด?
๋์ ์๋น (์ปจํธ๋กค๋ฌ์์ ๋ค ์ฒ๋ฆฌํจ):
์ฃผ๋ฐฉ์ฅ(Service)์ด ์๋ฆฌ๋ฅผ ํ์ ์ด. ํ์๋น ์ง์(Controller)์ด ์ฃผ๋ฐฉ์ ๋ค์ด๊ฐ์ "์์ด๊ณ ๋งํ๋ค!" ํ๊ณ ์ง์ ์๋ํํ
๋ฌ๋ ค๊ฐ "์๋ฆฌ๊ฐ ํ์ ๋ชป ๋จน์ต๋๋ค, 500์ ๋๋ ค๋๋ฆด๊ฒ์"๋ผ๊ณ ์ฌ๊ณผํด. ์ง์์ด 100๋ช
์ด๋ฉด ์ฌ๊ณผ๋ฒ๋ 100๊ฐ์ง์ผ.
์ข์ ์๋น (์์ธ ํํฐ ์ฌ์ฉ):
์ฃผ๋ฐฉ์ฅ์ ๊ทธ๋ฅ ์๋ฆฌ๋ฅผ ํ์ฐ๋ฉด ์ฃผ๋ฐฉ ๋ฐ๋ฅ์ ๋ถ๋ ์คํฐ์ปค(Exception)๋ฅผ ํ ๋์ง๊ณ ์ฟจํ๊ฒ ๋์์ ๋ฒ๋ ค!
ํ์๋น ์ง์๋ ์ ๊ฒฝ ์ ์จ. ๋์ ์๋น์๋ '๊ณ ๊ฐ ๋ถ๋ง ์ฒ๋ฆฌ ์ ๋ด๋ฐ(Exception Filter)' ์ด๋ผ๋ ๋ถ์๊ฐ ์จ์ด์์ง. ์ฃผ๋ฐฉ์์ ๋๊ฐ ๋ถ๋ ์คํฐ์ปค๋ฅผ ๋์ง๋ ์๊ฐ 0.1์ด ๋ง์ ์ ๋ดํ์ด ์ถ๋ํด์ ๋์์ฑ ๋ค์, "๊ณ ๊ฐ๋, ๋ฉ๋ด๊ฐ ํ์ ์
๋๋ค(๊น๋ํ JSON ํํ)" ๋ผ๋ฉฐ ๋งค์ฐ ์น์ ํ๊ณ ํต์ผ๋ ํค์ผ๋ก ์๋ํ๋ ๊ฑฐ์ผ.
๐งฉ ๋ด์ฅ ์์ธ์ ์ปค์คํ ์์ธ ๐ก
๐ฏ ์ด ์น์ ์ ์ฝ๊ณ ๋๋ฉด:
- ์ํฉ์ ๋ง๋ HTTP ์์ธ ํด๋์ค๋ฅผ ๋์ง(
throw) ์ค ์๋ค.
NestJS๊ฐ ์ค๋นํด ๋ ๊ธฐ๋ณธ ์์ธ๋ค (๋ฌด๊ธฐ๊ณ )
NestJS๋ HttpException์ ์์๋ฐ์ ์๋ง์ ์ํ์ฝ๋๋ณ ๋ฌด๊ธฐ๋ค์ ์ด๋ฏธ ๋ค ์ค๋นํด๋จ์ด.
import { NotFoundException, BadRequestException, ConflictException } from '@nestjs/common';
@Injectable()
export class UsersService {
async getProfile(id: string) {
const user = await this.db.findById(id);
// ์ ์ ๊ฐ ์๋ค๋ฉด ํํฐ์๊ฒ ํญํ(์์ธ)์ ๋์ง!
if (!user) {
// 404 ์ํ์ฝ๋์ ๋ฉ์์ง๊ฐ ๋ด๊ธด ์์ธ ๋ฐ์
throw new NotFoundException(`ํ์๋ฒํธ ${id}๋ฅผ ์ฐพ์ ์ ์์ต๋๋ค.`);
}
return user;
}
}[์ฃผ์ ๋ด์ฅ ์์ธ ๋ชฉ๋ก]
BadRequestException(400) - ํ๋ผ๋ฏธํฐ๋ ๋ฐ๋๊ฐ์ด ํ๋ ธ์ (04์ฅ ์ ํจ์ฑ ๊ฒ์ฌ ํ์ดํ๊ฐ ๋ฑ๋ ๊ฒ๋ ์ด๊ฑฐ์ผ)UnauthorizedException(401) - ํ ํฐ ์์/ํ๋ฆผ (06์ฅ ๊ฐ๋๊ฐ ๋ฑ์)NotFoundException(404) - ๋ฆฌ์์ค๋ฅผ ๋ชป ์ฐพ์ConflictException(409) - ์ด๋ฉ์ผ ์ค๋ณต๊ฐ์ , ์ด๋ฏธ ์ข์์ ๋๋ฆ ๋ฑ ๋ก์ง ์ถฉ๋InternalServerErrorException(500) - DB๊ฐ ์ฃฝ์๊ฑฐ๋ ๋ด ์ฝ๋์ ๋ฒ๊ทธ๊ฐ ์๊ฑฐ๋
๋๋ง์ ์ปค์คํ ์์ธ ๋ง๋ค๊ธฐ
"์์ก ๋ถ์กฑ" ๊ฐ์ ๋น์ฆ๋์ค ํนํ ์๋ฌ๋ 400 ์๋ฌ์ง๋ง, ํ๋ฒํ 400 ์๋ฌ์๋ ๊ตฌ๋ถ ์ง์ด์ฃผ๊ณ ์ถ์ด!
// common/exceptions/business.exception.ts
export class InsufficientBalanceException extends BadRequestException {
constructor(required: number, current: number) {
// ํ๋ก ํธ์๋์๊ฒ ์ค JSON ๋ฐ์ดํฐ๋ฅผ ์์ธํ๊ฒ ๋ง์๋๋ก ์กฐ๋ฆฝ!
super({
errorCode: 'INSUFFICIENT_BALANCE',
message: '๋์ด ๋ถ์กฑํฉ๋๋ค ใ
ใ
',
details: { required, current } // ๋จ์ ์์ก์ ์๋ ค์ค
});
}
}๐งฉ ์ ์ญ ์์ธ ํํฐ (Global Exception Filter) ๐ข
์ด์ ์ฃผ๋ฐฉ(์๋น์ค/์ปจํธ๋กค๋ฌ)์์ ๋์ง๋ ๋ชจ๋ ์๋ฌ๋ฅผ ํ๊ณต์์ ๋์์ฑ์, ์์ ํฌ์ฅ์ง์ ๋ด์์ฃผ๋ '๋ถ๋ง ์ฒ๋ฆฌ ์ ๋ด๋ฐ'์ ๋ง๋ค ์ฐจ๋ก์ผ.
// common/filters/http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import { Response } from 'express'; // Fastify ์ฐ๋ฉด FastifyReply ์ ์ฉ
@Catch() // ๐ ์๋ฌด ๊ดํธ๋ ์ ์ ์ผ๋ฉด '์๋ฌ๋๋ ๋ชจ๋ ๊ฒ'์ ๋ค ์ก์์ฑ๊ฒ ๋ค๋ ๋ป!
export class GlobalExceptionFilter implements ExceptionFilter {
// ์๋ฌ๊ฐ ํธ๋ฆฌ๊ฑฐ๋๋ ์๊ฐ ์ฌ๊ธฐ๊ฐ ๋ฌด์กฐ๊ฑด ์คํ๋จ
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest<Request>();
// 1. ์ํ ์ฝ๋ ํ๋ณ (๋ด๊ฐ ๋์ง ์๋ฌ๋ฉด ๊ทธ ์ฝ๋, ์๋๋ฉด ๋ฌด์กฐ๊ฑด 500 ์ฒ๋ฆฌ)
const status =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
// 2. ์ต์ข
ํ๋ก ํธ์๋์๊ฒ ๋ด๋ ค๊ฐ ํต์ผ๋ JSON ํฌ๋งท!
response.status(status).json({
success: false, // ๋ฌด์กฐ๊ฑด ๋ค์ด๊ฐ๋ ๊ณตํต ์ํ
timestamp: new Date().toISOString(),
path: request.url,
// ๋ด๊ฐ ๋์ง ์๋ฌ๋ผ๋ฉด ์๋งน์ด๋ฅผ ๋นผ์ค๊ณ , ์ ์ ์๋ ์๋ฌ๋ฉด ์์ ๋ฉ์ธ์ง
error: exception instanceof HttpException
? exception.getResponse()
: '์! ์๋ฒ ๋ด๋ถ ๋คํธ์ํฌ๋ง ์ค๋ฅ์
๋๋ค!',
});
}
}์ด ํํฐ๋ฅผ ๊ฑด๋ฌผ ์ ์ฒด์ ํ์ฑํ์ํค๋ฉด ๋์ด์ผ. main.ts ๋ก ๊ฐ๋ค!
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// ๊ธ๋ก๋ฒ ํํฐ ์ฅ์ฐฉ! ์ด์ 365์ผ ์๋ฌ ์ ๋์นจ.
app.useGlobalFilters(new GlobalExceptionFilter());
await app.listen(3000);
}๐งช ๋ฐ๋ผํด๋ณด๊ธฐ: ๊ถ๊ทน์ ๊ธ๋ก๋ฒ ํํฐ ๊น์๋ณด๊ธฐ
์ค๋ฌด์์๋ NestJS ์ ํจ์ฑ ๊ฒ์ฌ ์๋ฌ, Zod ๋ผ์ด๋ธ๋ฌ๋ฆฌ ๊ฒ์ฌ ์๋ฌ, ์ ์ ์๋ ์น๋ช ์ 500 ์๋ฌ ๋ฑ์ ํํฐ ์์์ ๋ถ๊ธฐ ์ฒ๋ฆฌํด์ผ ํด. ๊ฑฐ๊ธฐ์ 500 ์๋ฌ ๋ฐ์ ์ ํฐ๋ฏธ๋ ๋ก๊น ๊น์ง ๋ถ์ฌ๋ณด์.
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus, Logger } from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
import { ZodError } from 'zod'; // Zod ์ธ ๋
@Catch()
export class MegaGlobalFilter implements ExceptionFilter {
// ๋ก๊ฑฐ ์ฅ์ฐฉ
private readonly logger = new Logger(MegaGlobalFilter.name);
// HttpAdapterHost๋ฅผ ์ฐ๋ฉด Express๋ Fastify๋ ํ๋ ์์ํฌ ์ข
์์ฑ ์์ด ์๋ต(Reply) ๊ฐ๋ฅ!
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: unknown, host: ArgumentsHost) {
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const request = ctx.getRequest();
let status = HttpStatus.INTERNAL_SERVER_ERROR;
let message = '์ ์ ์๋ ์๋ฌ๊ฐ ๋ฐ์ํ์ต๋๋ค.';
let errorCode = 'UNKNOWN_ERROR';
// ๋ถ๊ธฐ 1: ๋ด๊ฐ ์๊ณ ๋์ง HTTP ์๋ฌ (400, 401, 404 ๋ฑ)
if (exception instanceof HttpException) {
status = exception.getStatus();
const res = exception.getResponse() as any;
message = typeof res === 'string' ? res : (res.message || 'Error');
errorCode = res.errorCode || 'HTTP_ERROR';
}
// ๋ถ๊ธฐ 2: Zod ์คํค๋ง ๊ฒ์ฆ ์คํจ ์๋ฌ
else if (exception instanceof ZodError) {
status = HttpStatus.BAD_REQUEST;
message = '์
๋ ฅ๊ฐ์ด ์ฌ๋ฐ๋ฅด์ง ์์ต๋๋ค.';
errorCode = 'ZOD_VALIDATION_ERROR';
}
// ๋ถ๊ธฐ 3: ์ง์ง ์๋ฒ๊ฐ ํฐ์ง๋ ๋ฒ๊ทธ (null.abc ์ ๊ทผ, DB ์ ์ ์ง์ฐ ๋ฑ) => 500
else {
// ๐จ ์น๋ช
์ ์๋ฌ๋ ๋ฌด์กฐ๊ฑด ์์ธํ๊ฒ ๋ก๊น
! (๋๋ ์ฌ๊ธฐ์ ์ฌ๋ API ํธ์ถ)
this.logger.error(
`[${request.method}] ${request.url} - ${exception}`,
(exception as Error)?.stack,
);
}
// ํต์ผ๋ ์๋ต ๊ท๊ฒฉ
const responseBody = {
statusCode: status,
errorCode,
message,
timestamp: new Date().toISOString(),
path: httpAdapter.getRequestUrl(request),
};
httpAdapter.reply(ctx.getResponse(), responseBody, status);
}
}์ฑ์ ๋ฑ๋กํ ๋๋ HttpAdapterHost ์ฃผ์
์ ์น์ฌํ๊ฒ ์ฃผ์ํด์ผ ํด:
// main.ts
const httpAdapterHost = app.get(HttpAdapterHost); // ์ด๋ํฐ ๊ฐ์ ธ์ค๊ธฐ!
app.useGlobalFilters(new MegaGlobalFilter(httpAdapterHost));๐ผ ๋ฒ ์คํธ ํ๋ํฐ์ค์ ์ค๋ฌด ํ ๐ก
1. ์ปจํธ๋กค๋ฌ ์์์ ๋ช
์์ ์ธ try-catch๋ ์ต๋ํ ์ง์ด๋ค.
๊ฐ์ฅ ๋ง์ด ํ๋ ์ด๋ณด์ ์ค์๊ฐ ๋ชจ๋ ๋ผ์ฐํฐ๋ง๋ค try-catch๋ฅผ ๋ง์์ฐ๋ ๊ฑฐ์ผ.
"๋ง์ฝ ๋ก์ง ์ง๋ค ์๋ฌ ๋๋ฉด ์ด๋กํด์?" -> ์ด์ฐจํผ ๊ธ๋ก๋ฒ ํํฐ๊ฐ ๋ค ์ก์! ๊ทธ๋ฅ ์๋น์ค ํจ์ ์ญ ์ฐ๊ณ ๋ง์ฝ ์ด์ํ๋ฉด ์๋น์ค ์์์ throw new NotFoundException() ๋ง ๋์ ธ ๋ฒ๋ ค. ์ปจํธ๋กค๋ฌ๋ ์ธ์ ํธํ๊ฒ ๋ก์ง๋ง ํธ์ถํ๋ ๊ฒ ๊ฐ์ฅ ์๋ฒฝํ ํํ์ผ.
2. ์๋ฌ ์ฝ๋๋ ํ ๊ณณ์ ๋ชจ์ ์์๋ก ๊ด๋ฆฌํ๋ผ
ํ๋์ฝ๋ฉ๋ ์๋ฌ ์ฝ๋๋ฅผ ๋ง๊ตฌ ์ฐ๋ฉด, ๋์ค์ ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๊ฐ ๋ถ๊ธฐ ์ฒ๋ฆฌํ ๋ ๊ณ ํต์ค๋ฌ์.
// common/error-codes.ts
export const ErrorCodes = {
USER_NOT_FOUND: 'USER_NOT_FOUND',
EMAIL_EXISTS: 'EMAIL_EXISTS',
TOKEN_EXPIRED: 'TOKEN_EXPIRED',
} as const;
// ์ฌ์ฉํ ๋
throw new BadRequestException({ errorCode: ErrorCodes.EMAIL_EXISTS });๐ฅ ์๋ฌ ํด๊ฒฐ ์นดํ๋ก๊ทธ
์๋ฌ ๋ฉ์์ง๊ฐ ๋จ๋ฉด Ctrl+F๋ก ๊ฒ์ํด๋ด.
โ UnhandledPromiseRejectionWarning (์๋ฒ ๋ค์ด)
ํ์:
์๋ฒ๊ฐ ์์ ํ ํน ๊บผ์ง. ๋ฌด์ธ๊ฐ ์ฒ๋ฆฌ๋์ง ์์ ์์ธ๋ผ๋ฉฐ ๋ป๊ฑด ๊ธ์จ ์ฅ๋ฌธ์ด ํฐ๋ฏธ๋์ ๋ธ.
์์ธ:
๋๋๊ฒ๋ ๊ธ๋ก๋ฒ ์์ธ ํํฐ๋ฅผ ๊ฑฐ์น์ง ์์ ์๋ฌ์ผ. ์ด ๊ฒฝ์ฐ๋ ๋น๋๊ธฐ ํจ์ (Promise) ์์์ ์๋ฌ๊ฐ ๋ฌ๋๋ฐ await๋ฅผ ์ ๊ฑธ์ด์ค์ ํ์ด๋ฐ์ด ์ด๊ธ๋ ์ฑ ์ฐ์ฃผ ๋ฐ ๋ฏธ์๋ก ์๋ฌ๊ฐ ํฐ์ ธ๋ฒ๋ฆฐ ๊ฒฝ์ฐ์ผ.
ํด๊ฒฐ์ฑ
:
์๋ฌ๊ฐ ๋ Promise๋ฅผ ๋ฆฌํดํ๋ ํจ์(์: DB ์ ์ฅ ๋ก์ง) ์์ await๋ฅผ ์ ๊ธฐ์ฌํ๋์ง ์ฒดํฌ!
โ TypeError: Cannot read properties of undefined (reading 'switchToHttp')
์์ธ:
ํํฐ์์ ArgumentsHost๋ฅผ ์จ์ผ ํ๋๋ฐ ๋ญ๊ฐ ๊ฐ์ฒด ๊ตฌ์กฐ ์ธ์์ ์๋ชปํ์. ์ฃผ๋ก WebSocket์ด๋ GraphQL ๊ฐ์ด HTTP๊ฐ ์๋ ํ๊ฒฝ์์ ๊ฐ์ ๋ก .switchToHttp()๋ฅผ ์ต์ง๋ก ๊บผ๋ด๋ ค ํ ๋ ๋ฐ์ํ๊ธฐ๋ ํด.
ํด๊ฒฐ์ฑ
:
๋ง์ฝ HTTP ์ ์ฉ ์ฑ์ด ์๋๋ผ๋ฉด host.getType() === 'http' ๋ก ๋ถ๊ธฐ ์ฒ๋ฆฌ๋ฅผ ํด์ค์ผ ์์ ํด.
๐๏ธ ์นํธ์ํธ โ ์ค๋ฌด ์์ฝ ์นด๋
| ์ํฉ | ์ฌ์ฉ ์์ | ๋น๊ณ |
|---|---|---|
| ๋ฆฌ์์ค ๋ชป ์ฐพ์ | throw new NotFoundException() | HTTP 404 ๋ด๋ ค๊ฐ |
| ๋น์ฆ๋์ค ์๋ฌ | throw new BadRequestException() | ๋๋ถ๋ถ์ ์๋ฌ (400) |
| ํ์ดํ/๊ฐ๋ ๋ฐ์์ ๋ค ์ก๊ธฐ | @Catch() ๋ฌ๋ฆฐ ํด๋์ค ์์ฑ | ๊ธ๋ก๋ฒ ํํฐ ์์ฑ |
| ์ ์ญ ํํฐ ์ ์ฉ | app.useGlobalFilters(new MyFilter()) | main.ts ํ์ ๊ธฐ์ฌ |
๐ ๋ง๋ฌด๋ฆฌ ํด์ฆ
Q1. NestJS์์ ์ข์ ์ํคํ ์ฒ๋ฅผ ๋ง๋ค๊ธฐ ์ํด ๊ถ์ฅํ๋ ์๋ฌ ์ฒ๋ฆฌ ๋ฐฉ์ ์ค ๊ฐ์ฅ ์ณ์ ๊ฒ์?
- A) ๋ชจ๋ ์ปจํธ๋กค๋ฌ์ ๋ผ์ฐํฐ ๋ด๋ถ์
try { } catch() { }๋ธ๋ก์ ์์ฑํ์ฌ ์ง์ 500 ์ํ ์ฝ๋๋ฅผres.send()ํ๋ค. - B) ์๋น์ค(Service) ๋ ์ด์ด์ ๋ชจ๋ ๋ฐํ๊ฐ์ ์๋ฌ์ธ์ง ํ์ธํ๋
if (result.error)๊ฒ์ฌ๋ฅผ ์ด์ดํ ์ง ๋ค. - C) ์ปจํธ๋กค๋ฌ์์ ์์ธ ์ฒ๋ฆฌ ๊ณ ๋ฏผ์ ๋๊ณ , ๋ก์ง ์ค ๋ฌธ์ ๊ฐ ์๊ธฐ๋ฉด ์์ธ(
Exception)๋ฅผthrowํ์ฌ ์ ์ญ ํํฐ๊ฐ ์ฒ๋ฆฌํ๊ฒ ๋งก๊ธด๋ค.
โ ์ ๋ต: C
๐ก ์ค๋ช : ํํฐ์ ์กด์ฌ ์ด์ ์ผ. ํ์๋น ์ง์(Controller)์ ์๋ฆฌ ์ฃผ๋ฌธ๋ง ๋ฐ๊ณ ๊ฐ๋ค์ฃผ๊ณ ํธํ๊ฒ ์ผํด์ผ ํด. ๋๋ฐ ์ํฉ ์ฒ๋ฆฌ๋ ๋ถ๋ง ์ฒ๋ฆฌ๋ฐ(Global Exception Filter)์ ์ค์์ง๊ถํ ์์ผ์ ํต์ผ๋๊ฒ ์๋ตํ๋ ๊ฒ์ด ์ต๊ณ ์ผ.
Q2. ํ๋ก ํธ์๋ ๊ฐ๋ฐ์๊ฐ API ์๋ต์ ๋ฐ์๋๋ฐ ์๋ฌ ํํ๊ฐ ํ์ฑํ๊ธฐ ๋๋ฌด ํ๋ค๋ค๊ณ ํญ์ํ๋ค. ์ด๋ฅผ ์์ ํ ๊ท๊ฒฉํ(์: { success: false, msg: "...", code: "..." }) ํด์ ํ ๋ฐฉ์ ์ฒ๋ฆฌํ๊ณ ์ถ์๋ฐ, ๊ผญ ์์ฑํด์ผ ํ๋ ํด๋์ค์ ๋ฐ์ฝ๋ ์ดํฐ์ ์์ ํ์
์ ๋ฌด์์ธ๊ฐ?
โ
์ ๋ต: @Catch() ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ๋ถ์ด๊ณ , ExceptionFilter ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํ(implements)ํ ํํฐ ๊ฐ์ฒด๋ฅผ ์์ฑํ๋ค!
Q3. ๋๋ฒ๊น
ํด์ฆ: ์๋ ์ฝ๋์์ HttpAdapterHost๋ฅผ ๋ค๋ฃฐ ๋ ๋นผ๋จน์ด์ ์ ๋ main.ts ๋ฑ๋ก ๊ณผ์ ์ ์์ ํด๋ด.
@Catch()
export class HttpFilter implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
}โ
์ ๋ต: ๋น๋๋ app ๊ฐ์ฒด์์ ์ด๋ํฐ๋ฅผ ๊ฐ์ ธ์ ์ธ์๋ก ๋ฃ์ด์ค์ผ ํ๋ค.
์ค๋ช
: const httpAdapterHost = app.get(HttpAdapterHost); ์ด๋ ๊ฒ app.get() ๋ช
๋ น์ด๋ก ์ปจํ
์ด๋์์ ๊บผ๋ด์จ ๋ค์ app.useGlobalFilters(new HttpFilter(httpAdapterHost)) ์ ๊ฐ์ด ๋ฃ์ด์ค์ผ ์์กด์ฑ์ด ๊นจ์ง์ง ์์.