[NestJS] Interceptors
정의
NestJS Interceptor 는 요청 처리 전/후에 로직을 삽입하는 클래스입니다. AOP (Aspect-Oriented Programming) 패턴의 구현체로, 로깅, 캐싱, 응답 변환, 예외 매핑, RxJS operator 활용 등에 사용합니다.
기본 구조
import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { tap } from 'rxjs/operators';
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const start = Date.now();
return next.handle().pipe(
tap(() => console.log(`Duration: ${Date.now() - start}ms`)),
);
}
}
intercept(context, next): 요청 처리 전 로직next.handle(): 실제 route handler (Observable 반환)- RxJS operators (
tap,map,catchError,timeout) 로 파이프라인 구성
부착 방법
Method
@Get()
@UseInterceptors(LoggingInterceptor)
findAll() { ... }
Controller
@Controller('users')
@UseInterceptors(LoggingInterceptor)
export class UsersController { ... }
Global
// main.ts
app.useGlobalInterceptors(new LoggingInterceptor());
// 또는 module (DI 지원)
import { APP_INTERCEPTOR } from '@nestjs/core';
@Module({
providers: [
{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
],
})
export class AppModule {}
주요 사용 사례
1. Logging
@Injectable()
export class LoggingInterceptor implements NestInterceptor {
private readonly logger = new Logger('LoggingInterceptor');
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const req = context.switchToHttp().getRequest();
const {method, url} = req;
const start = Date.now();
this.logger.log(`${method} ${url}`);
return next.handle().pipe(
tap({
next: () => {
const duration = Date.now() - start;
this.logger.log(`${method} ${url} ${duration}ms`);
},
error: (err) => {
const duration = Date.now() - start;
this.logger.error(`${method} ${url} ${duration}ms - ${err.message}`);
},
}),
);
}
}
2. Response Transformation
응답을 일관된 wrapper 형식으로:
interface Response<T> {
data: T;
timestamp: string;
path: string;
}
@Injectable()
export class TransformInterceptor<T> implements NestInterceptor<T, Response<T>> {
intercept(context: ExecutionContext, next: CallHandler): Observable<Response<T>> {
const req = context.switchToHttp().getRequest();
return next.handle().pipe(
map(data => ({
data,
timestamp: new Date().toISOString(),
path: req.url,
})),
);
}
}
3. Cache
@Injectable()
export class CacheInterceptor implements NestInterceptor {
constructor(
@Inject(CACHE_MANAGER) private cache: Cache,
) {}
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
const req = context.switchToHttp().getRequest();
const key = `${req.method}:${req.url}`;
const cached = await this.cache.get(key);
if (cached) return of(cached);
return next.handle().pipe(
tap(async (data) => {
await this.cache.set(key, data, 60);
}),
);
}
}
@nestjs/cache-manager 의 내장 CacheInterceptor 활용 관용.
4. Timeout
import { timeout, catchError, TimeoutError } from 'rxjs/operators';
import { throwError } from 'rxjs';
@Injectable()
export class TimeoutInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
timeout(5000),
catchError(err => {
if (err instanceof TimeoutError) {
return throwError(() => new RequestTimeoutException());
}
return throwError(() => err);
}),
);
}
}
5. Class Serialization
ClassSerializerInterceptor (내장) 는 @Exclude(), @Expose() 데코레이터 기반 응답 필터.
import { Exclude, Expose } from 'class-transformer';
export class UserEntity {
id: number;
email: string;
@Exclude()
password: string;
@Expose({groups: ['admin']})
role: string;
constructor(partial: Partial<UserEntity>) {
Object.assign(this, partial);
}
}
@Controller('users')
@UseInterceptors(ClassSerializerInterceptor)
export class UsersController {
@Get(':id')
async findOne(@Param('id') id: string) {
return new UserEntity(await this.service.findOne(id));
// password 자동 제외
}
}
6. Exception Mapping
@Injectable()
export class ExceptionMapInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
catchError(err => {
if (err instanceof PrismaClientKnownRequestError) {
if (err.code === 'P2002') {
return throwError(() => new ConflictException('Duplicate entry'));
}
if (err.code === 'P2025') {
return throwError(() => new NotFoundException('Not found'));
}
}
return throwError(() => err);
}),
);
}
}
Interceptor 순서
여러 개는 nested 실행:
@UseInterceptors(A, B, C)
- 요청: A -> B -> C -> handler
- 응답: handler -> C -> B -> A
tap 등 side effect 순서 주의.
Async Interceptor
async intercept(context: ExecutionContext, next: CallHandler): Promise<Observable<any>> {
const user = await this.userService.findByToken(...);
return next.handle();
}
Promise 도 리턴 가능. Nest 가 자동 unwrap.
Global 등록 순서
Guards -> Interceptors (before) -> Pipes -> Handler -> Interceptors (after) -> Exception Filters
Guards 가 먼저 실행. Interceptor 는 next.handle() 전후로 감쌈.
실전 조합
API 응답 통일 + 로깅 + timeout
// main.ts
app.useGlobalInterceptors(
new LoggingInterceptor(),
new TimeoutInterceptor(),
new TransformInterceptor(),
);
또는 module 로:
@Module({
providers: [
{ provide: APP_INTERCEPTOR, useClass: LoggingInterceptor },
{ provide: APP_INTERCEPTOR, useClass: TimeoutInterceptor },
{ provide: APP_INTERCEPTOR, useClass: TransformInterceptor },
],
})
export class AppModule {}
함정
WARNING
ClassSerializerInterceptor + Fastify. Fastify 는 기본 JSON.stringify 성능이 매우 빠른데, class-transformer 를 거치면 오히려 느려질 수 있음. 벤치마크.
CAUTION
Interceptor 안에서 응답 조작. map() 으로 새 객체 반환 필수. 원본 mutate 는 side effect.
WARNING
@Res() 사용 endpoint 에는 interceptor after 로직 안 실행. passthrough: true 로 우회 가능.
IMPORTANT
Global interceptor 에서 특정 route 우회. Reflector + custom decorator (@SkipInterceptor()) 로.
CAUTION
RxJS 학습 부담. tap, map, catchError, throwError 는 기본. 복잡한 파이프는 이해하기 어려움.
관련 위키
- NestJS - 상위 개요
- Controllers
- Guards - 이전 단계
- Pipes - 다음 단계
- Exception Filters - 예외 처리
- Middleware
- Modules
- Providers
- FastAPI Middleware - 대비
- Koa Middleware - 대비 (onion model)
이 글의 용어 (10개)
- [FastAPI] Middlewarefastapi
- 정의 FastAPI Middleware 는 모든 요청과 응답을 가로채 로깅, 인증, 압축, CORS 등 공통 처리를 하는 계층입니다. Starlette 의 ASGI middlew…
- [Framework] NestJSframeworks
- 정의 NestJS 는 Kamil Myśliwiec 이 2017년 발표한 Node.js 서버측 프레임워크 입니다. TypeScript first, Angular 에서 영감받은 모듈…
- [Koa] Middleware (Onion Model)frameworks
- 정의 Koa Middleware 는 요청/응답 사이에 실행되는 async 함수입니다. 각 미들웨어는 (context) 와 (다음 미들웨어를 실행하는 함수) 를 받고, 로 down…
- [NestJS] Controllersframeworks
- 정의 NestJS Controller 는 데코레이터가 붙은 클래스로, HTTP 요청을 라우팅하고 응답을 반환합니다. Method 별로 , , , , 데코레이터를 붙여 URL + …
- [NestJS] Exception Filtersframeworks
- 정의 NestJS Exception Filter 는 애플리케이션에서 발생한 예외를 catch 하여 클라이언트에게 응답 형식을 통일합니다. 데코레이터로 어떤 예외를 처리할지 지정합…
- [NestJS] Guardsframeworks
- 정의 NestJS Guard 는 특정 요청이 controller method 에 도달하기 전에 실행되어 접근 허용 여부 를 결정합니다. Middleware 이후, Intercep…
- [NestJS] Middlewareframeworks
- 정의 NestJS Middleware 는 route handler 실행 이전, Guard 도 실행되기 전에 실행되는 함수/클래스입니다. Express/Fastify 표준 midd…
- [NestJS] Modulesframeworks
- 정의 NestJS Module 은 데코레이터가 붙은 클래스로, 관련된 controller, provider, import/export 를 하나로 묶는 조직 단위입니다. Angul…
- [NestJS] Pipes (Validation, Transformation)frameworks
- 정의 NestJS Pipe 는 controller method 로 들어가는 인자를 변환 (transformation) 하거나 검증 (validation) 하는 클래스입니다. Gu…
- [NestJS] Providers & Dependency Injectionframeworks
- 정의 NestJS Provider 는 데코레이터가 붙은 클래스 (또는 값/factory) 로, DI container 에 등록되어 다른 클래스에 주입됩니다. Service, Re…
💬 댓글