๋ฏธ๋ค์จ์ด
๋ฏธ๋ค์จ์ด (Middleware)๋ ๋ผ์ฐํธ ํธ๋ค๋ฌ๊ฐ ํธ์ถ๋๊ธฐ ์ ์ ์คํ๋๋ ํจ์์ ๋๋ค. ๋ฏธ๋ค์จ์ด ํจ์๋ ์์ฒญ (request) ๊ฐ์ฒด, ์๋ต (response) ๊ฐ์ฒด, ๊ทธ๋ฆฌ๊ณ ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฒญ-์๋ต ์ฌ์ดํด ๋ด์์ ๋ค์ ๋ฏธ๋ค์จ์ด ํจ์ (next)์ ์ ๊ทผํ ์ ์์ต๋๋ค. ์ด next ํจ์๋ ์ผ๋ฐ์ ์ผ๋ก next๋ผ๋ ์ด๋ฆ์ ๋ณ์๋ก ํํ๋ฉ๋๋ค.
Nest ๋ฏธ๋ค์จ์ด๋ ๊ธฐ๋ณธ์ ์ผ๋ก Express ๋ฏธ๋ค์จ์ด์ ๋์ผํ ๋ฐฉ์์ผ๋ก ๋์ํฉ๋๋ค. ๋ค์์ ๊ณต์ Express ๋ฌธ์์์ ์ค๋ช ํ ๋ฏธ๋ค์จ์ด์ ๊ธฐ๋ฅ์ ๋๋ค:
๋ฏธ๋ค์จ์ด ํจ์๋ ๋ค์๊ณผ ๊ฐ์ ์์ ์ ์ํํ ์ ์์ต๋๋ค:
- ์ด๋ค ์ฝ๋๋ ์คํํ ์ ์๋ค.
- ์์ฒญ (request) ๋ฐ ์๋ต (response) ๊ฐ์ฒด๋ฅผ ์์ ํ ์ ์๋ค.
- ์์ฒญ-์๋ต ์ฌ์ดํด์ ์ข ๋ฃํ ์ ์๋ค.
- ์คํ ๋ด ๋ค์ ๋ฏธ๋ค์จ์ด ํจ์๋ฅผ ํธ์ถํ ์ ์๋ค.
- ๋ง์ฝ ํ์ฌ ๋ฏธ๋ค์จ์ด ํจ์๊ฐ ์์ฒญ-์๋ต ์ฌ์ดํด์ ์ข ๋ฃํ์ง ์๋๋ค๋ฉด, next()๋ฅผ ๋ฐ๋์ ํธ์ถํด์ผ ํ๋ฉฐ, ๊ทธ๋ ์ง ์์ผ๋ฉด ์์ฒญ์ด ์ฒ๋ฆฌ๋์ง ์์ ์ฑ ๋ฉ์ถฐ ์๊ฒ ๋ฉ๋๋ค.
Nest์์ ์ปค์คํ ๋ฏธ๋ค์จ์ด๋ ํจ์ ํํ ๋๋ @Injectable() ๋ฐ์ฝ๋ ์ดํฐ๊ฐ ๋ถ์ ํด๋์ค ํํ๋ก ๊ตฌํํ ์ ์์ต๋๋ค. ํด๋์ค ๋ฐฉ์์์๋ NestMiddleware ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ผ ํฉ๋๋ค. ํจ์ ๋ฐฉ์์ ํน๋ณํ ์๊ตฌ์ฌํญ ์์ด ์ผ๋ฐ ํจ์๋ก ์ ์ํ๋ฉด ๋ฉ๋๋ค. ์ฐ์ , ํด๋์ค ๊ธฐ๋ฐ์ผ๋ก ๊ฐ๋จํ ๋ฏธ๋ค์จ์ด ๊ธฐ๋ฅ์ ๊ตฌํํด ๋ณด๊ฒ ์ต๋๋ค.
๊ฒฝ๊ณ
Express์ Fastify๋ ๋ฏธ๋ค์จ์ด๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ด ๋ค๋ฅด๋ฉฐ, ๋ฉ์๋ ์๊ทธ๋์ฒ ๋ํ ์๋ก ๋ค๋ฆ ๋๋ค. ์์ธํ ๋ด์ฉ์ ์ฌ๊ธฐ์์ ํ์ธํ์ธ์.
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log('Request...');
next();
}
}
์์กด์ฑ ์ฃผ์
Nest์ ๋ฏธ๋ค์จ์ด๋ ์์กด์ฑ ์ฃผ์ (Dependendy Injection)์ ์๋ฒฝํ๊ฒ ์ง์ํฉ๋๋ค. ํ๋ก๋ฐ์ด๋๋ ์ปจํธ๋กค๋ฌ์ ๋ง์ฐฌ๊ฐ์ง๋ก, ๋ฏธ๋ค์จ์ด๋ ๊ฐ์ ๋ชจ๋ ๋ด์ ๋ฑ๋ก๋ ์์กด์ฑ๋ค์ ์ฃผ์ ๋ฐ์ ์ ์์ต๋๋ค. ์ด ์ญ์ ์ผ๋ฐ์ ์ผ๋ก ์์ฑ์ (constructor)๋ฅผ ํตํด ์ํ๋ฉ๋๋ค.
๋ฏธ๋ค์จ์ด ์ ์ฉ
@Module() ๋ฐ์ฝ๋ ์ดํฐ ์์๋ ๋ฏธ๋ค์จ์ด๋ฅผ ์ค์ ํ๋ ํญ๋ชฉ์ด ์์ต๋๋ค. ๋์ , ๋ฏธ๋ค์จ์ด๋ ๋ชจ๋ ํด๋์ค์ configure() ๋ฉ์๋ ์์์ ์ค์ ํฉ๋๋ค. ๋ฏธ๋ค์จ์ด๋ฅผ ํฌํจํ๋ ๋ชจ๋์ NestModule ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ผ ํ๋ฉฐ, ์ฌ๊ธฐ์๋ AppModule์์ LoggerMiddleware๋ฅผ ์ค์ ํด ๋ณด๊ฒ ์ต๋๋ค.
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes('cats');
}
}
์ ์์์์๋ LoggerMiddleware๋ฅผ ์ด์ ์ ์ ์ํ CatsController์ /cats ๋ผ์ฐํธ ํธ๋ค๋ฌ์ ์ค์ ํ์ต๋๋ค. ๋ํ ๋ฏธ๋ค์จ์ด๋ฅผ ํน์ HTTP ์์ฒญ ๋ฉ์๋์๋ง ์ ํํ๊ณ ์ถ์ ๊ฒฝ์ฐ, forRoutes() ๋ฉ์๋์ ๊ฒฝ๋ก (path)์ ํจ๊ป ์์ฒญ ๋ฉ์๋ (request method)๋ฅผ ํฌํจํ ๊ฐ์ฒด๋ฅผ ์ ๋ฌํ์ฌ ์ค์ ํ ์ ์์ต๋๋ค. ์๋ ์์ ์ฒ๋ผ ์์ฒญ ๋ฉ์๋๋ฅผ ์ง์ ํ๋ ค๋ฉด RequestMethod ์ด๊ฑฐํ (enum)์ import ํด์ผ ํฉ๋๋ค.
import { Module, NestModule, RequestMethod, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes({ path: 'cats', method: RequestMethod.GET });
}
}
ํํธ
configure() ๋ฉ์๋๋ async/await์ ์ฌ์ฉํ์ฌ ๋น๋๊ธฐ ๋ฐฉ์์ผ๋ก ๊ตฌํํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, configure() ๋ฉ์๋ ๋ด๋ถ์์ ๋น๋๊ธฐ ์์ ์ ์๋ฃ๋ฅผ ๊ธฐ๋ค๋ฆด ์ ์์ต๋๋ค.
๊ฒฝ๊ณ
Express ์ด๋ํฐ๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ, NestJS ์ ํ๋ฆฌ์ผ์ด์ ์ ๊ธฐ๋ณธ์ ์ผ๋ก body-parser ํจํค์ง์ json ๋ฐ urlencoded ๋ฏธ๋ค์จ์ด๋ฅผ ๋ฑ๋กํฉ๋๋ค. ๋ฐ๋ผ์ MiddlewareConsumer๋ฅผ ํตํด ํด๋น ๋ฏธ๋ค์จ์ด๋ฅผ ์ปค์คํฐ๋ง์ด์ง ํ๋ ค๋ฉด, NestFactory.create()๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ ์์ฑํ ๋ bodyParser ํ๋๊ทธ๋ฅผ false๋ก ์ค์ ํ์ฌ ์ ์ญ ๋ฏธ๋ค์จ์ด๋ฅผ ๋นํ์ฑํํด์ผ ํฉ๋๋ค.
๋ผ์ฐํธ ์์ผ๋์นด๋
NestJS ๋ฏธ๋ค์จ์ด๋ ํจํด ๊ธฐ๋ฐ ๋ผ์ฐํธ๋ฅผ ์ง์ํฉ๋๋ค. ์๋ฅผ ๋ค์ด, ์ด๋ฆ ์๋ ์์ผ๋์นด๋ (*splat)๋ฅผ ์ฌ์ฉํ๋ฉด ๊ฒฝ๋ก์์ ์์์ ๋ฌธ์ ์กฐํฉ๊ณผ ๋งค์นญํ ์ ์์ต๋๋ค. ์๋ ์์์์๋ abcd/๋ก ์์ํ๋ ๋ชจ๋ ๊ฒฝ๋ก์ ๋ํด ํด๋น ๋ฏธ๋ค์จ์ด๊ฐ ์คํ๋ฉ๋๋ค. ๊ฒฝ๋ก ๋ค์ ๋ช ๊ธ์๊ฐ ์ค๋ ์๊ด์์ต๋๋ค.
forRoutes({
path: 'abcd/*splat',
method: RequestMethod.ALL,
});
ํํธ
splat์ ๋จ์ํ ์์ผ๋์นด๋ ํ๋ผ๋ฏธํฐ์ ์ด๋ฆ์ผ ๋ฟ์ด๋ฉฐ, ํน๋ณํ ์๋ฏธ๋ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด *wildcard์ฒ๋ผ ์ํ๋ ์ด๋ฆ์ผ๋ก ์ง์ ํ ์ ์์ต๋๋ค.
'abcd/*' ๊ฒฝ๋ก๋ abcd/1, abcd/123, abcd/abc ๋ฑ๊ณผ ๊ฐ์ด abcd/๋ก ์์ํ๋ ๋ชจ๋ ๊ฒฝ๋ก์ ์ผ์นํฉ๋๋ค. ํ์ดํ(-)๊ณผ ์ (.)์ ๋ฌธ์์ด ๊ธฐ๋ฐ ๊ฒฝ๋ก์์ ๋ฌธ์ ๊ทธ๋๋ก ํด์๋ฉ๋๋ค. ๊ทธ๋ฌ๋ abcd/์ ๊ฐ์ด ์ถ๊ฐ ๋ฌธ์๊ฐ ์๋ ๊ฒฝ๋ก๋ ์ด ๊ฒฝ๋ก์ ์ผ์นํ์ง ์์ต๋๋ค. ์ด๋ฌํ ๊ฒฝ์ฐ ์์ผ๋์นด๋๋ฅผ ์ ํ์ ์ผ๋ก ๋ง๋ค๊ธฐ ์ํด ์ค๊ดํธ๋ก ๊ฐ์ธ์ผํฉ๋๋ค.
forRoutes({
path: 'abcd/{*splat}',
method: RequestMethod.ALL,
});
๋ฏธ๋ค์จ์ด ์๋น์
๋ฏธ๋ค์จ์ด ์๋น์ (Middleware consumer)๋ NestJS์์ ๋ฏธ๋ค์จ์ด๋ฅผ ๊ด๋ฆฌํ๊ธฐ ์ํ ํฌํผ ํด๋์ค์ ๋๋ค. ์ด ํด๋์ค๋ ์ฌ๋ฌ ๋ด์ฅ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋ฉฐ, ๋ชจ๋ fluent ์คํ์ผ๋ก ์ฒด์ด๋ ํ ์ ์์ต๋๋ค. forRoutes() ๋ฉ์๋๋ ๋จ์ผ ๋ฌธ์์ด, ์ฌ๋ฌ ๋ฌธ์์ด, RouteInfo ๊ฐ์ฒด, ์ปจํธ๋กค๋ฌ ํด๋์ค, ์ฌ์ง์ด ์ฌ๋ฌ ์ปจํธ๋กค๋ฌ ํด๋์ค๋ฅผ ๋ฐ์ ์ ์์ต๋๋ค. ๋๋ถ๋ถ์ ๊ฒฝ์ฐ, ๋จ์ํ ์ผํ๋ก ๊ตฌ๋ถ๋ ์ปจํธ๋กค๋ฌ ๋ชฉ๋ก์ ์ ๋ฌํ๋ ๋ฐฉ์์ ์ฌ์ฉํ ๊ฒ์ ๋๋ค. ์๋๋ ๋จ์ผ ์ปจํธ๋กค๋ฌ๋ฅผ ์ฌ์ฉํ ์์์ ๋๋ค:
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes(CatsController);
}
}
ํํธ
apply() ๋ฉ์๋๋ ๋จ์ผ ๋ฏธ๋ค์จ์ด๋ฅผ ๋ฐ์ ์๋ ์๊ณ , ์ฌ๋ฌ ๊ฐ์ ๋ฏธ๋ค์จ์ด๋ฅผ ์ง์ ํ๊ธฐ ์ํด ์ฌ๋ฌ ์ธ์๋ฅผ ๋ฐ์ ์๋ ์์ต๋๋ค.
๊ฒฝ๋ก ์ ์ธํ๊ธฐ
๊ฒฝ์ฐ์ ๋ฐ๋ผ ํน์ ๊ฒฝ๋ก์๋ ๋ฏธ๋ค์จ์ด๊ฐ ์ ์ฉ๋์ง ์๋๋ก ์ ์ธํ๊ณ ์ถ์ ์ ์์ต๋๋ค. ์ด๋ exclude() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ์ฌ ์ฝ๊ฒ ๋ฌ์ฑํ ์ ์์ต๋๋ค. exclude() ๋ฉ์๋๋ ์ ์ธํ ๊ฒฝ๋ก๋ฅผ ์๋ณํ๊ธฐ ์ํด ๋จ์ผ ๋ฌธ์์ด, ์ฌ๋ฌ ๋ฌธ์์ด, ๋๋ RouteInfo ๊ฐ์ฒด๋ฅผ ์ธ์๋ก ๋ฐ์ ์ ์์ต๋๋ค. ๋ค์์ ์ด๋ฅผ ์ฌ์ฉํ๋ ์์์ ๋๋ค:
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/{*splat}',
)
.forRoutes(CatsController);
ํํธ
exclude() ๋ฉ์๋๋ path-to-regexp ํจํค์ง๋ฅผ ์ฌ์ฉํ์ฌ ์์ผ๋์นด๋ ํ๋ผ๋ฏธํฐ๋ฅผ ์ง์ํฉ๋๋ค.
์ ์์ ์์ LoggerMiddleware๋ CatsController ๋ด์ ์ ์๋ ๋ชจ๋ ๋ผ์ฐํธ์ ์ ์ฉ๋์ง๋ง, exclude() ๋ฉ์๋์ ์ ๋ฌ๋ ์ธ ๊ฐ์ง ๋ผ์ฐํธ๋ ์์ธ์ ๋๋ค.
์ด๋ฌํ ๋ฐฉ์์ ํน์ ๋ผ์ฐํธ๋ ๋ผ์ฐํธ ํจํด์ ๋ฐ๋ผ ๋ฏธ๋ค์จ์ด๋ฅผ ์ ์ฉํ๊ฑฐ๋ ์ ์ธํ๋ ๋ฐ ์ ์ฐ์ฑ์ ์ ๊ณตํฉ๋๋ค.
ํจ์ํ ๋ฏธ๋ค์จ์ด
์ง๊ธ๊น์ง ์ฌ์ฉํ LoggerMiddleware ํด๋์ค๋ ๋งค์ฐ ๋จ์ํฉ๋๋ค. ๋ฉค๋ฒ๋ ์๊ณ , ์ถ๊ฐ ๋ฉ์๋๋ ์์กด์ฑ๋ ์์ต๋๋ค. ๊ทธ๋ ๋ค๋ฉด ์ด๊ฑธ ๊ตณ์ด ํด๋์ค๋ก ์ ์ํ์ง ์๊ณ , ๊ฐ๋จํ ํจ์๋ก ์ ์ํ ์๋ ์์๊น์? ์ค์ ๋ก ๊ฐ๋ฅํฉ๋๋ค. ์ด๋ฌํ ์ ํ์ ๋ฏธ๋ค์จ์ด๋ฅผ ํจ์ํ ๋ฏธ๋ค์จ์ด (functional middleware)๋ผ๊ณ ํฉ๋๋ค. ๋ค์์ ํด๋์ค ๊ธฐ๋ฐ ๋ฏธ๋ค์จ์ด๋ฅผ ํจ์ํ์ผ๋ก ๋ณํํ ์์์ ๋๋ค:
import { Request, Response, NextFunction } from 'express';
export function logger(req: Request, res: Response, next: NextFunction) {
console.log(`Request...`);
next();
};
๊ทธ๋ฆฌ๊ณ AppModule ๋ด์์ ์ด๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค:
consumer
.apply(logger)
.forRoutes(CatsController);
ํํธ
๋ฏธ๋ค์จ์ด์ ์์กด์ฑ์ด ํ์ํ์ง ์๋ค๋ฉด, ๋ ๊ฐ๋จํ ํจ์ํ ๋ฏธ๋ค์จ์ด ๋ฐฉ์์ ์ฌ์ฉํ๋ ๊ฒ์ ๊ณ ๋ คํด ๋ณด์ธ์.
๋ค์ค ๋ฏธ๋ค์จ์ด
์์ ์ธ๊ธํ ๊ฒ์ฒ๋ผ, ์์ฐจ์ ์ผ๋ก ์คํ๋๋ ์ฌ๋ฌ ๋ฏธ๋ค์จ์ด๋ฅผ ๋ฐ์ธ๋ฉํ๋ ค๋ฉด apply() ๋ฉ์๋ ์์ ์ผํ๋ก ๊ตฌ๋ถ๋ ๋ชฉ๋ก์ ์ ๊ณตํ๋ฉด ๋ฉ๋๋ค.
consumer.apply(cors(), helmet(), logger).forRoutes(CatsController);
์ ์ญ ๋ฏธ๋ค์จ์ด
๋ชจ๋ ๋ฑ๋ก๋ ๋ผ์ฐํธ์ ํ ๋ฒ์ ๋ฏธ๋ค์จ์ด๋ฅผ ๋ฐ์ธ๋ฉํ๊ณ ์ถ๋ค๋ฉด, INestApplication ์ธ์คํด์ค๊ฐ ์ ๊ณตํ๋ use() ๋ฉ์๋๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค:
const app = await NestFactory.create(AppModule);
app.use(logger);
await app.listen(process.env.PORT ?? 3000);
ํํธ
์ ์ญ ๋ฏธ๋ค์จ์ด์์ DI ์ปจํ ์ด๋์ ์ ๊ทผํ๋ ๊ฒ์ ๋ถ๊ฐ๋ฅํฉ๋๋ค. app.use()๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ ํจ์ํ ๋ฏธ๋ค์จ์ด (functional middleware)๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ๋๋ ํด๋์ค ๊ธฐ๋ฐ ๋ฏธ๋ค์จ์ด๋ฅผ ์ฌ์ฉํ๊ณ , AppModule (๋๋ ๋ค๋ฅธ ๋ชจ๋) ๋ด์์ .forRoutes('*')๋ก ์ ์ฉํ ์๋ ์์ต๋๋ค.
์์ธ ํํฐ
Nest๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ๋ฐ์ ๋ชจ๋ ์ฒ๋ฆฌ๋์ง ์์ ์์ธ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ด์ฅ ์์ธ ์ฒ๋ฆฌ ๊ณ์ธต์ ์ ๊ณตํฉ๋๋ค. ์ ํ๋ฆฌ์ผ์ด์ ์ฝ๋์์ ์์ธ๋ฅผ ์ฒ๋ฆฌํ์ง ์์ผ๋ฉด, ์ด ๊ณ์ธต์ด ํด๋น ์์ธ๋ฅผ ํฌ์ฐฉํ๊ณ ์๋์ผ๋ก ์ ์ ํ ์ฌ์ฉ์ ์นํ์ ์ธ ์๋ต์ ์ ์กํฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก ์ด ๋์์ ์ ์ญ ๋ด์ฅ ์์ธ ํํฐ์ ์ํด ์ํ๋๋ฉฐ, ์ด ํํฐ๋ HttpException ๋ฐ ๊ทธ ํ์ ํด๋์ค ํ์ ์ ์์ธ๋ฅผ ์ฒ๋ฆฌํฉ๋๋ค. ์์ธ๊ฐ HttpException์ด ์๋๊ฑฐ๋ ์ด๋ฅผ ์์๋ฐ์ ํด๋์ค๊ฐ ์๋ ๊ฒฝ์ฐ, ๋ด์ฅ ์์ธ ํํฐ๋ ๋ค์๊ณผ ๊ฐ์ ๊ธฐ๋ณธ JSON ์๋ต์ ์์ฑํฉ๋๋ค:
{
"statusCode": 500,
"message": "Internal server error"
}
ํํธ
์ ์ญ ์์ธ ํํฐ๋ http-errors ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ๋ถ๋ถ์ ์ผ๋ก ์ง์ํฉ๋๋ค. ๊ธฐ๋ณธ์ ์ผ๋ก, statusCoide์ message ์์ฑ์ ํฌํจํ๋ ์์ธ๊ฐ ๋ฐ์ํ๋ฉด, ํด๋น ์ ๋ณด๊ฐ ์ ๋๋ก ๋ฐ์๋์ด ์๋ต์ผ๋ก ์ ์ก๋ฉ๋๋ค (์ธ์๋์ง ์์ ์์ธ์ ๋ํด ๊ธฐ๋ณธ์ ์ผ๋ก ์ฌ์ฉ๋๋ InternalServerErrorException ๋์ ์).
ํ์ค ์์ธ ๋์ง๊ธฐ
Nest๋ @nestjs/common ํจํค์ง์์ ์ ๊ณต๋๋ ๋ด์ฅ HttpException ํด๋์ค๋ฅผ ์ ๊ณตํฉ๋๋ค. ์ผ๋ฐ์ ์ธ HTTP REST/GraphQL API ๊ธฐ๋ฐ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ํน์ ์ค๋ฅ ์กฐ๊ฑด์ด ๋ฐ์ํ์ ๋ ํ์ค HTTP ์๋ต ๊ฐ์ฒด๋ฅผ ๋ณด๋ด๋ ๊ฒ์ด ๋ชจ๋ฒ ์ฌ๋ก์ ๋๋ค.
์๋ฅผ ๋ค์ด, CatsController์ findAll() ๋ฉ์๋ (GET ๋ผ์ฐํธ ํธ๋ค๋ฌ)๊ฐ ์๋ค๊ณ ๊ฐ์ ํด ๋ด ์๋ค. ์ด ํธ๋ค๋ฌ๊ฐ ์ด๋ค ์ด์ ๋ก ์์ธ๋ฅผ ๋์ง๋ค๊ณ ๊ฐ์ ํ๋ฉด, ๋ค์๊ณผ ๊ฐ์ด ํ๋์ฝ๋ฉํ ์ ์์ต๋๋ค.
@Get()
async findAll() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
ํํธ
์ฌ๊ธฐ์ ์ฌ์ฉํ HttpStatus๋ @nestjs/common ํจํค์ง์์ ๊ฐ์ ธ์จ ํฌํผ enum์ ๋๋ค.
ํด๋ผ์ด์ธํธ๊ฐ ์ด ์๋ํฌ์ธํธ๋ฅผ ํธ์ถํ๋ฉด, ์๋ต์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
{
"statusCode": 403,
"message": "Forbidden"
}
HttpException ์์ฑ์๋ ์๋ต์ ๊ฒฐ์ ํ๋ ๋ ๊ฐ์ ํ์ ์ธ์๋ฅผ ๋ฐ์ต๋๋ค:
- response ์ธ์๋ JSON ์๋ต ๋ณธ๋ฌธ์ ๊ฒฐ์ ํ๋ฉฐ, ๋ฌธ์์ด์ด๋ ๊ฐ์ฒด๊ฐ ๋ ์ ์์ต๋๋ค.
- status ์ธ์๋ HTTP ์ํ ์ฝ๋๋ฅผ ์ ์ํฉ๋๋ค.
๊ธฐ๋ณธ์ ์ผ๋ก JSON ์๋ต ๋ณธ๋ฌธ์๋ ๋ ๊ฐ์ ์์ฑ์ด ํฌํจ๋ฉ๋๋ค.
- statusCode: status ์ธ์๋ก ์ ๊ณต๋ HTTP ์ํ ์ฝ๋
- message: ํด๋น ์ํ ์ฝ๋์ ๊ธฐ๋ฐํ ์งง์ ์ค๋ฅ ์ค๋ช
message๋ง ๋ฎ์ด์ฐ๊ณ ์ถ๋ค๋ฉด response ์ธ์์ ๋ฌธ์์ด์ ์ ๋ฌํ๋ฉด ๋ฉ๋๋ค. ์๋ต ๋ณธ๋ฌธ ์ ์ฒด๋ฅผ ๋ฎ์ด์ฐ๋ ค๋ฉด response ์ธ์์ ๊ฐ์ฒด๋ฅผ ์ ๋ฌํ๋ฉด ๋๊ณ , Nest๊ฐ ์ด๋ฅผ JSON์ผ๋ก ์ง๋ ฌํํ์ฌ ์๋ต ๋ณธ๋ฌธ์ผ๋ก ๋ฐํํฉ๋๋ค.
๋ ๋ฒ์งธ ์ธ์์ธ status๋ ์ ํจํ HTTP ์ํ ์ฝ๋์ฌ์ผ ํ๋ฉฐ, ๋ณดํต @nestjs/common์์ ์ ๊ณตํ๋ HttpStatus enum์ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ถ์ฅ๋ฉ๋๋ค.
์ ํ์ ์ธ ์ธ ๋ฒ์งธ ์ธ์์ธ options๋ ์ค๋ฅ์ ์์ธ์ ๋ด์ cause ๊ฐ์ฒด๋ฅผ ์ ๊ณตํ๋ ๋ฐ ์ฌ์ฉํ ์ ์์ต๋๋ค. ์ด cause๋ ์๋ต ๊ฐ์ฒด์ ์ง๋ ฌํ๋์ง ์์ง๋ง, ๋ก๊น ์ ์ ์ฉํ๊ฒ ํ์ฉํ ์ ์์ต๋๋ค. ์ฆ, HttpException์ด ๋ฐ์ํ ๋ด๋ถ ์์ธ์ ๋ํ ์ ๋ณด๋ฅผ ํฌํจํ ์ ์์ต๋๋ค.
๋ค์์ ์๋ต ๋ณธ๋ฌธ ์ ์ฒด๋ฅผ ๋ฎ์ด์ฐ๊ณ ์ค๋ฅ ์์ธ์ ์ ๊ณตํ๋ ์์์ ๋๋ค:
@Get()
async findAll() {
try {
await this.service.findAll()
} catch (error) {
throw new HttpException({
status: HttpStatus.FORBIDDEN,
error: 'This is a custom message',
}, HttpStatus.FORBIDDEN, {
cause: error
});
}
}
์์ ๊ฐ์ด ์ ์ฒด ์๋ต์ ๊ฐ์ฒด๋ก ์ค๋ฒ๋ผ์ด๋ํ๋ฉด, ํด๋ผ์ด์ธํธ์๊ฒ ๋ฐํ๋๋ ์๋ต์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
{
"status": 403,
"error": "This is a custom message"
}
์์ธ ๋ก๊น
๊ธฐ๋ณธ์ ์ผ๋ก, Nest์ ์์ธ ํํฐ๋ HttpException๊ณผ ๊ทธ๊ฒ์ ์์ํ ์์ธ๋ค๊ณผ ๊ฐ์ ๋ด์ฅ ์์ธ๋ฅผ ๋ก๊น ํ์ง ์์ต๋๋ค. ์ด๋ฌํ ์์ธ๊ฐ ๋ฐ์ํด๋ ์ฝ์์ ํ์๋์ง ์์ผ๋ฉฐ, ์ด๋ ์ผ๋ฐ์ ์ธ ์ ํ๋ฆฌ์ผ์ด์ ํ๋ฆ์ ์ผ๋ถ๋ก ๊ฐ์ฃผ๋๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด์ ๋์ผํ ๋์์ WsException, RpcException ๊ฐ์ ๋ค๋ฅธ ๋ด์ฅ ์์ธ์๋ ์ ์ฉ๋ฉ๋๋ค.
์ด๋ฌํ ์์ธ๋ค์ ๋ชจ๋ @nestjs/common ํจํค์ง์์ ๋ด๋ณด๋ด๋ IntrinsicException ๊ธฐ๋ณธ ํด๋์ค๋ฅผ ์์ํฉ๋๋ค. ์ด ํด๋์ค๋ ์ผ๋ฐ์ ์ธ ์ ํ๋ฆฌ์ผ์ด์ ๋์์ ์ผ๋ถ์ธ ์์ธ์ ๊ทธ๋ ์ง ์์ ์์ธ๋ฅผ ๊ตฌ๋ถํ๋ ๋ฐ ๋์์ ์ค๋๋ค.
์ด๋ฌํ ์์ธ๋ฅผ ๋ก๊น ํ๊ณ ์ถ๋ค๋ฉด, ์ปค์คํ ์์ธ ํํฐ๋ฅผ ์์ฑํ๋ฉด ๋ฉ๋๋ค. ๋ค์ ์น์ ์์ ๊ทธ ๋ฐฉ๋ฒ์ ์ค๋ช ํฉ๋๋ค.
์ปค์คํ ์์ธ
๋๋ถ๋ถ์ ๊ฒฝ์ฐ, ์ปค์คํ ์์ธ๋ฅผ ์์ฑํ ํ์ ์์ด Nest์์ ์ ๊ณตํ๋ ๋ด์ฅ HTTP ์์ธ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. ํ์ง๋ง ์ปค์คํ ์์ธ๊ฐ ํ์ํ ๊ฒฝ์ฐ์๋, HttpException ํด๋์ค๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ์์ธ ๊ณ์ธต์ ์ง์ ๊ตฌํํ๋ ๊ฒ์ด ์ข์ ๊ดํ์ ๋๋ค. ์ด๋ฌํ ๋ฐฉ์์ผ๋ก ๊ตฌํํ๋ฉด, Nest๊ฐ ํด๋น ์์ธ๋ฅผ ์ธ์ํ๊ณ ์๋์ผ๋ก ์ค๋ฅ ์๋ต์ ์ฒ๋ฆฌํด ์ค๋๋ค. ๋ค์์ ์ด๋ฌํ ์ปค์คํ ์์ธ๋ฅผ ๊ตฌํํ๋ ์์์ ๋๋ค.
export class ForbiddenException extends HttpException {
constructor() {
super('Forbidden', HttpStatus.FORBIDDEN);
}
}
ForbiddenException์ ๊ธฐ๋ณธ HttpException ํด๋์ค๋ฅผ ์์ํ๋ฏ๋ก, Nest์ ๋ด์ฅ ์์ธ ์ฒ๋ฆฌ๊ธฐ์ ์ํํ๊ฒ ์๋ํฉ๋๋ค. ๋ฐ๋ผ์ findAll() ๋ฉ์๋ ๋ด๋ถ์์ ํด๋น ์์ธ๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
@Get()
async findAll() {
throw new ForbiddenException();
}
๋ด์ฅ HTTP ์์ธ๋ค
Nest๋ ๊ธฐ๋ณธ HttpException์ ์์ํ๋ ํ์ค ์์ธ๋ค์ ์ ๊ณตํฉ๋๋ค. ์ด ์์ธ๋ค์ @nestjs/common ํจํค์ง์์ ์ ๊ณต๋๋ฉฐ, ๊ฐ์ฅ ์ผ๋ฐ์ ์ธ HTTP ์์ธ๋ค์ ํํํฉ๋๋ค:
- BadRequestException
- UnauthorizedException
- NotFoundException
- ForbiddenException
- NotAcceptableException
- RequestTimeoutException
- ConflictException
- GoneException
- HttpVersionNotSupportedException
- PayloadTooLargeException
- UnsupportedMediaTypeException
- UnprocessableEntityException
- InternalServerErrorException
- NotImplementedException
- ImATeapotException
- MethodNotAllowedException
- BadGatewayException
- ServiceUnavailableException
- GatewayTimeoutException
- PreconditionFailedException
๋ชจ๋ ๋ด์ฅ ์์ธ๋ options ํ๋ผ๋ฏธํฐ๋ฅผ ์ฌ์ฉํ์ฌ ์๋ฌ ์์ธ (cause)๊ณผ ์๋ฌ ์ค๋ช (description)์ ํจ๊ป ์ ๊ณตํ ์ ์์ต๋๋ค.
throw new BadRequestException('Something bad happened', {
cause: new Error(),
description: 'Some error description',
});
์์ ๊ฐ์ด ์ฌ์ฉํ๋ฉด, ์๋ต์ ๋ค์๊ณผ ๊ฐ์ ํํ๋ก ๋ฐํ๋ฉ๋๋ค.
{
"message": "Something bad happened",
"error": "Some error description",
"statusCode": 400
}
์์ธ ํํฐ
๊ธฐ๋ณธ(๋ด์ฅ) ์์ธ ํํฐ๋ ๋ง์ ๊ฒฝ์ฐ๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌํด ์ฃผ์ง๋ง, ์์ธ ์ฒ๋ฆฌ ๊ณ์ธต์ ์์ ํ ์ ์ดํ๊ณ ์ถ์ ๋๊ฐ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ๋ก๊น ์ ์ถ๊ฐํ๊ฑฐ๋ ๋์ ์ธ ์กฐ๊ฑด์ ๋ฐ๋ผ ๋ค๋ฅธ JSON ์คํค๋ง๋ฅผ ์ฌ์ฉํ๊ณ ์ถ์ ์ ์์ต๋๋ค. ์์ธ ํํฐ๋ ์ด๋ฌํ ๋ชฉ์ ์ ์ํด ์ค๊ณ๋์์ผ๋ฉฐ, ์์ธ ์ฒ๋ฆฌ ํ๋ฆ๊ณผ ํด๋ผ์ด์ธํธ์ ๋ฐํ๋๋ ์๋ต ๋ด์ฉ์ ์ง์ ์ ์ดํ ์ ์๊ฒ ํด ์ค๋๋ค.
์ด์ HttpException ํด๋์ค์ ์ธ์คํด์ค๋ฅผ ํฌ์ฐฉํ๊ณ , ๊ทธ์ ๋ํ ์ฌ์ฉ์ ์ ์ ์๋ต ๋ก์ง์ ๊ตฌํํ๋ ์์ธ ํํฐ๋ฅผ ๋ง๋ค์ด๋ด ์๋ค. ์ด๋ฅผ ์ํด ๊ธฐ๋ณธ ํ๋ซํผ์ Request ๋ฐ Response ๊ฐ์ฒด์ ์ ๊ทผํด์ผ ํฉ๋๋ค. Request ๊ฐ์ฒด์์๋ ์๋์ URL์ ์ถ์ถํด ๋ก๊น ์ ๋ณด์ ํฌํจ์ํค๊ณ , Response ๊ฐ์ฒด์์๋ response.json() ๋ฉ์๋๋ฅผ ํตํด ์๋ต์ ์ง์ ์ ์ดํฉ๋๋ค.
import { ExceptionFilter, Catch, ArgumentsHost, HttpException } from '@nestjs/common';
import { Request, Response } from 'express';
@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,
});
}
}
ํํธ
๋ชจ๋ ์์ธ ํํฐ๋ ์ ๋ค๋ฆญ ExceptionFilter<T> ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํด์ผ ํฉ๋๋ค. ์ด ์ธํฐํ์ด์ค๋ catch(exception: T, host: ArgumentsHost) ๋ฉ์๋๋ฅผ ํด๋น ์๊ทธ๋์ฒ๋ก ์ ๊ณตํด์ผ ํฉ๋๋ค. ์ฌ๊ธฐ์ T๋ ์์ธ์ ํ์ ์ ๋ํ๋ ๋๋ค.
๊ฒฝ๊ณ
@nestjs/platform-fastify๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, response.json() ๋์ response.send()๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค. fastify์์ ์ฌ๋ฐ๋ฅธ ํ์ ์ ์ํฌํธ ํ๋ ๊ฒ์ ์์ง ๋ง์ธ์.
@Catch(HttpException) ๋ฐ์ฝ๋ ์ดํฐ๋ ์์ธ ํํฐ์ ํ์ํ ๋ฉํ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ธ๋ฉํ์ฌ, Nest์๊ฒ ์ด ํํฐ๊ฐ HttpException ํ์ ์ ์์ธ๋ง์ ์ฒ๋ฆฌํ๋๋ก ์๋ ค์ค๋๋ค. @Catch() ๋ฐ์ฝ๋ ์ดํฐ๋ ํ๋์ ์ธ์๋ฅผ ๋ฐ์ ์๋ ์๊ณ , ์ผํ๋ก ๊ตฌ๋ถ๋ ์ฌ๋ฌ ์ธ์๋ฅผ ๋ฐ์ ์๋ ์์ต๋๋ค. ์ด๋ฅผ ํตํด ์ฌ๋ฌ ์ข ๋ฅ์ ์์ธ๋ฅผ ํ ๋ฒ์ ์ฒ๋ฆฌํ๋ ํํฐ๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.
Arguments host
catch() ๋ฉ์๋์ ํ๋ผ๋ฏธํฐ๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค. exception ํ๋ผ๋ฏธํฐ๋ ํ์ฌ ์ฒ๋ฆฌ ์ค์ธ ์์ธ ๊ฐ์ฒด์ด๋ฉฐ, host ํ๋ผ๋ฏธํฐ๋ ArgumentsHost ๊ฐ์ฒด์ ๋๋ค. ArgumentsHost๋ ๋งค์ฐ ๊ฐ๋ ฅํ ์ ํธ๋ฆฌํฐ ๊ฐ์ฒด๋ก, ์์ธํ ๋ด์ฉ์ ์คํ ์ปจํ ์คํธ ์ฑํฐ์์ ๋ค๋ฃน๋๋ค.
์ด ์ฝ๋ ์์ ์์๋ ArgumentsHost๋ฅผ ์ฌ์ฉํด ์์ธ๊ฐ ๋ฐ์ํ ์ปจํธ๋กค๋ฌ์ ์๋ ์์ฒญ ํธ๋ค๋ฌ์ ์ ๋ฌ๋ Request์ Response ๊ฐ์ฒด์ ์ ๊ทผํฉ๋๋ค. ์ด๋ฅผ ์ํด ArgumentsHost์ ์ ์๋ ํฌํผ ๋ฉ์๋๋ค์ ํ์ฉํฉ๋๋ค. ArgumentsHost์ ๋ํด ๋ ์์๋ณด๋ ค๋ฉด ์ฌ๊ธฐ์์ ํ์ธํ์ธ์.
์ด์ฒ๋ผ ์ถ์ํ๋ ์ด์ ๋ ArgumentsHost๊ฐ ๋ชจ๋ ์คํ ์ปจํ ์คํธ (์: ํ์ฌ ์ฌ์ฉ ์ค์ธ HTTP ์๋ฒ ์ปจํ ์คํธ๋ฟ ์๋๋ผ ๋ง์ดํฌ๋ก์๋น์ค๋ WebSocket ์ปจํ ์คํธ ๋ฑ)์์ ์๋ํ๊ธฐ ๋๋ฌธ์ ๋๋ค. ์คํ ์ปจํ ์คํธ ์ฑํฐ์์ ์ฐ๋ฆฌ๋ ArgumentsHost์ ๊ทธ ํฌํผ ๋ฉ์๋๋ค์ ํตํด ์ด๋ค ์คํ ์ปจํ ์คํธ์์๋ ์ ์ ํ ์ธ์๋ค์ ์ ๊ทผํ๋ ๋ฐฉ๋ฒ์ ๋ฐฐ์ฐ๊ฒ ๋ฉ๋๋ค. ์ด๋ฅผ ํตํด ๋ชจ๋ ์ปจํ ์คํธ์์ ๋์ํ๋ ๋ฒ์ฉ์ ์ธ ์์ธ ํํฐ๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
ํํฐ ๋ฐ์ธ๋ฉํ๊ธฐ
์ด์ ์ฐ๋ฆฌ๊ฐ ๋ง๋ HttpExceptionFilter๋ฅผ CatsController์ create() ๋ฉ์๋์ ์ฐ๊ฒฐํด ๋ด ์๋ค.
@Post()
@UseFilters(new HttpExceptionFilter())
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
ํํธ
@UseFilters() ๋ฐ์ฝ๋ ์ดํฐ๋ @nestjs/common ํจํค์ง์์ ๊ฐ์ ธ์ต๋๋ค.
์ฌ๊ธฐ์ ์ฐ๋ฆฌ๋ @UseFilters() ๋ฐ์ฝ๋ ์ดํฐ๋ฅผ ์ฌ์ฉํ์ต๋๋ค. @Catch() ๋ฐ์ฝ๋ ์ดํฐ์ ๋ง์ฐฌ๊ฐ์ง๋ก, ๋จ์ผ ํํฐ ์ธ์คํด์ค ๋๋ ์ผํ๋ก ๊ตฌ๋ถ๋ ์ฌ๋ฌ ํํฐ ์ธ์คํด์ค๋ฅผ ์ธ์๋ก ๋ฐ์ ์ ์์ต๋๋ค. ์ ์์์์๋ HttpExceptionFilter ์ธ์คํด์ค๋ฅผ ์ง์ ์์ฑํด ์ ๋ฌํ์ต๋๋ค. ๋์ ํด๋์ค ์์ฒด๋ฅผ ์ ๋ฌํ์ฌ Nest ํ๋ ์์ํฌ๊ฐ ์ธ์คํด์ค๋ฅผ ์์ฑํ๊ฒ ํ ์๋ ์์ผ๋ฉฐ, ์ด ๊ฒฝ์ฐ ์์กด์ฑ ์ฃผ์ ๋ ๊ฐ๋ฅํฉ๋๋ค.
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
ํํธ
๊ฐ๋ฅํ ๊ฒฝ์ฐ ํํฐ๋ฅผ ์ธ์คํด์ค๊ฐ ์๋ ํด๋์ค๋ก ์ ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค. Nest๋ ๋์ผํ ํด๋์ค์ ์ธ์คํด์ค๋ฅผ ๋ชจ๋ ์ ์ฒด์์ ์ฌ์ฌ์ฉํ ์ ์๊ธฐ ๋๋ฌธ์ ๋ฉ๋ชจ๋ฆฌ ์ฌ์ฉ๋์ ์ค์ผ ์ ์์ต๋๋ค.
์์ ์์ ์์๋ HttpExceptionFilter๊ฐ ๋จ์ผ create() ๋ผ์ฐํธ ํธ๋ค๋ฌ์๋ง ์ ์ฉ๋์ด ๋ฉ์๋ ๋ฒ์ (method-scoped)๋ก ์๋ํฉ๋๋ค. ์์ธ ํํฐ๋ ๋ฉ์๋ ๋ฒ์ (method-scoped), ์ปจํธ๋กค๋ฌ ๋ฒ์ (controller-scoped), ๋๋ ์ ์ญ ๋ฒ์ (global-scoped)๋ก ์ค์ ํ ์ ์์ต๋๋ค. ์๋ฅผ ๋ค์ด, ์ปจํธ๋กค๋ฌ ๋ฒ์๋ก ํํฐ๋ฅผ ์ค์ ํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ํ๋ฉด ๋ฉ๋๋ค:
@Controller()
@UseFilters(new HttpExceptionFilter())
export class CatsController {}
์ด ๊ตฌ์ฑ์ CatsController ๋ด์ ์ ์๋ ๋ชจ๋ ๋ผ์ฐํธ ํธ๋ค๋ฌ์ ๋ํด HttpExceptionFilter๋ฅผ ์ค์ ํฉ๋๋ค.
์ ์ญ ๋ฒ์ (global-scoped)์ ํํฐ๋ฅผ ์์ฑํ๋ ค๋ฉด ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ๋ฉด ๋ฉ๋๋ค:
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalFilters(new HttpExceptionFilter());
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
๊ฒฝ๊ณ
useGlobalFilters() ๋ฉ์๋๋ ๊ฒ์ดํธ์จ์ด (gateway)๋ ํ์ด๋ธ๋ฆฌ๋ ์ ํ๋ฆฌ์ผ์ด์ (hybrid applications)์๋ ํํฐ๋ฅผ ์ค์ ํ์ง ์์ต๋๋ค.
์ ์ญ ๋ฒ์ ํํฐ (Global-scoped filters)๋ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฒด์ ๊ฑธ์ณ ๋ชจ๋ ์ปจํธ๋กค๋ฌ ๋ฐ ๋ชจ๋ ๋ผ์ฐํธ ํธ๋ค๋ฌ์ ์ ์ฉ๋ฉ๋๋ค. ์์กด์ฑ ์ฃผ์ (Dependency Injection) ์ธก๋ฉด์์ ๋ณด๋ฉด, useGlobalFilters()๋ฅผ ์ฌ์ฉํด ๋ชจ๋ ์ธ๋ถ์์ ๋ฑ๋กํ ์ ์ญ ํํฐ๋ ์์กด์ฑ์ ์ฃผ์ ๋ฐ์ ์ ์์ต๋๋ค. ์ด๋ Nest์ ๋ชจ๋ ์ปจํ ์คํธ ๋ฐ๊นฅ์์ ํํฐ๊ฐ ์์ฑ๋๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด, ๋ชจ๋ ๋ด๋ถ์์ ์ ์ญ ํํฐ๋ฅผ ๋ฑ๋กํด์ผ ํ๋ฉฐ, ๋ค์๊ณผ ๊ฐ์ด ์ค์ ํ ์ ์์ต๋๋ค.
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}
ํํธ
์ด ๋ฐฉ์์ผ๋ก ํํฐ์ ๋ํ ์์กด์ฑ ์ฃผ์ ์ ์ํํ ๋๋, ์ด ๊ตฌ์ฑ์ด ์ด๋ค ๋ชจ๋์์ ์ฌ์ฉ๋์๋์ง์ ๊ด๊ณ์์ด ํด๋น ํํฐ๋ ์ฌ์ค์ ์ ์ญ (Global)์ด๋ผ๋ ์ ์ ์ ์ํ์ธ์. ๊ทธ๋ ๋ค๋ฉด ์ด ์์ ์ ์ด๋์์ ์ํํด์ผ ํ ๊น์? ์ ์์์์์ฒ๋ผ HttpExceptionFilter๊ฐ ์ ์๋ ๋ชจ๋์ ์ ํํ์ธ์. ๋ํ, useClass๋ ์ปค์คํ ํ๋ก๋ฐ์ด๋ ๋ฑ๋ก์ ์ฒ๋ฆฌํ๋ ์ ์ผํ ๋ฐฉ๋ฒ์ด ์๋๋๋ค. ์์ธํ ๋ด์ฉ์ ์ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํ์ธ์.
์ด ๋ฐฉ์์ผ๋ก ํํฐ๋ฅผ ๋ฑ๋กํ ๋๋ ํ์ํ ๋งํผ ์ฌ๋ฌ ๊ฐ์ ํํฐ๋ฅผ ๋ฑ๋กํ ์ ์์ต๋๋ค. ๋จ์ํ ๊ฐ๊ฐ์ ํํฐ๋ฅผ providers ๋ฐฐ์ด์ ์ถ๊ฐํ๋ฉด ๋ฉ๋๋ค.
Catch everything
๋ชจ๋ ์ฒ๋ฆฌ๋์ง ์์ ์์ธ๋ฅผ ํฌ์ฐฉํ๋ ค๋ฉด, @Catch() ๋ฐ์ฝ๋ ์ดํฐ์ ๋งค๊ฐ๋ณ์๋ฅผ ๋น์๋๋ฉด ๋ฉ๋๋ค. ์ฆ, @Catch()์ฒ๋ผ ์ฌ์ฉํ๋ฉด ๋ฉ๋๋ค.
์๋ ์์๋ ํ๋ซํผ์ ์์กดํ์ง ์๋ ์ฝ๋๋ก, Request๋ Response ๊ฐ์ ํ๋ซํผ ํน์ ๊ฐ์ฒด๋ฅผ ์ง์ ์ฌ์ฉํ์ง ์๊ณ , Nest์ HTTP ์ด๋ํฐ๋ฅผ ํตํด ์๋ต์ ์ ๋ฌํฉ๋๋ค.
import {
ExceptionFilter,
Catch,
ArgumentsHost,
HttpException,
HttpStatus,
} from '@nestjs/common';
import { HttpAdapterHost } from '@nestjs/core';
@Catch()
export class CatchEverythingFilter implements ExceptionFilter {
constructor(private readonly httpAdapterHost: HttpAdapterHost) {}
catch(exception: unknown, host: ArgumentsHost): void {
// ํน์ ์ํฉ์์๋ `httpAdapter`๊ฐ ์์ฑ์ ๋ฉ์๋์์ ์ฌ์ฉ ๋ถ๊ฐ๋ฅํ ์ ์์ผ๋ฏ๋ก,
// ์ฌ๊ธฐ์ ์ด๋ฅผ ํด๊ฒฐํด์ผ ํฉ๋๋ค.
const { httpAdapter } = this.httpAdapterHost;
const ctx = host.switchToHttp();
const httpStatus =
exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
const responseBody = {
statusCode: httpStatus,
timestamp: new Date().toISOString(),
path: httpAdapter.getRequestUrl(ctx.getRequest()),
};
httpAdapter.reply(ctx.getResponse(), responseBody, httpStatus);
}
}
๊ฒฝ๊ณ
๋ชจ๋ ์์ธ๋ฅผ ํฌ์ฐฉํ๋ ํํฐ์ ํน์ ํ์ ์ ๋ฐ์ธ๋ฉ๋ ํํฐ๋ฅผ ํจ๊ป ์ฌ์ฉํ ๊ฒฝ์ฐ, "๋ชจ๋ ์์ธ๋ฅผ ํฌ์ฐฉํ๋" ํํฐ๋ฅผ ๋จผ์ ์ ์ธํด์ผ ํน์ ํ์ ์ ๋ฐ์ธ๋ฉ๋ ํํฐ๊ฐ ํด๋น ํ์ ์ ์ฌ๋ฐ๋ฅด๊ฒ ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
์์
์ผ๋ฐ์ ์ผ๋ก๋ ์ ํ๋ฆฌ์ผ์ด์ ์๊ตฌ์ฌํญ์ ์ถฉ์กฑํ๊ธฐ ์ํด ์์ ํ ์ปค์คํฐ๋ง์ด์ง ๋ ์์ธ ํํฐ๋ฅผ ์์ฑํฉ๋๋ค. ํ์ง๋ง ๊ธฐ๋ณธ ์ ์ญ ์์ธ ํํฐ์ ๋์์ ์ผ๋ถ ์กฐ๊ฑด์ ๋ฐ๋ผ ๋ณ๊ฒฝํ๊ณ ์ถ์ ๋๋ ๋ด์ฅ๋ ๊ธฐ๋ณธ ์์ธ ํํฐ๋ฅผ ๋จ์ํ ์์ (extend) ํ๋ ๋ฐฉ์์ด ์ ์ฉํ ์ ์์ต๋๋ค.
์์ธ ์ฒ๋ฆฌ๋ฅผ ๊ธฐ๋ณธ ํํฐ์ ์์ํ๋ ค๋ฉด, BaseExceptionFilter๋ฅผ ํ์ฅํ๊ณ ์์๋ฐ์ catch() ๋ฉ์๋๋ฅผ ํธ์ถํ๋ฉด ๋ฉ๋๋ค.
import { Catch, ArgumentsHost } from '@nestjs/common';
import { BaseExceptionFilter } from '@nestjs/core';
@Catch()
export class AllExceptionsFilter extends BaseExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
super.catch(exception, host);
}
}
๊ฒฝ๊ณ
BaseExceptionFilter๋ฅผ ํ์ฅํ ๋ฉ์๋ ๋ฒ์ ๋๋ ์ปจํธ๋กค๋ฌ ๋ฒ์ ํํฐ๋ new ํค์๋๋ก ์ง์ ์ธ์คํด์ค๋ฅผ ์์ฑํด์๋ ์ ๋ฉ๋๋ค. ๋์ Nest ํ๋ ์์ํฌ๊ฐ ์๋์ผ๋ก ์ธ์คํด์ค๋ฅผ ์์ฑํ๋๋ก ํด์ผ ํฉ๋๋ค.
์ ์ญ ํํฐ๋ ๊ธฐ๋ณธ ํํฐ๋ฅผ ํ์ฅํ ์ ์์ผ๋ฉฐ, ์ด ์์ ์ ๋ ๊ฐ์ง ๋ฐฉ๋ฒ ์ค ํ๋๋ก ์ํํ ์ ์์ต๋๋ค.
์ฒซ ๋ฒ์งธ ๋ฐฉ๋ฒ์ ์ฌ์ฉ์ ์ ์ ์ ์ญ ํํฐ๋ฅผ ์ธ์คํด์คํํ ๋ HttpAdapter ์ฐธ์กฐ๋ฅผ ์ฃผ์ ํ๋ ๊ฒ์ ๋๋ค.
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new AllExceptionsFilter(httpAdapter));
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();
๋ ๋ฒ์งธ ๋ฐฉ๋ฒ์ APP_FILTER ํ ํฐ์ ์ฌ์ฉํ๋ ๊ฒ์ ๋๋ค. ์ฌ๊ธฐ์ ๊ฐ์ด ์ค์ ํ ์ ์์ต๋๋ค.
Reference
'๐ ๊ณต์ ๋ฌธ์ ๋ฒ์ญ > Nest.js' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[Nest.js] Overview - Interceptors, Custom decorators (0) | 2025.07.13 |
---|---|
[Nest.js] Overview - Pipes, Guards (0) | 2025.07.08 |
[Nest.js] Overview - Providers, Modules (0) | 2025.07.05 |
[Nest.js] Overview - First Steps, Controllers (0) | 2025.07.05 |
[Nest.js] Introduction (0) | 2025.07.04 |