NestJS에서 인터셉터(Interceptor)는 요청(Request)와 응답(Response)을 가로채서 추가적인 로직을 실행할 수 있는 기능을 제공한다. 인터셉터는 AOP(Aspect-Oriented Programming, 관점 지향 프로그래밍)의 한 형태이다.
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 request = context.switchToHttp().getRequest();
const method = request.method;
const url = request.url;
const now = Date.now();
console.log(`Incoming request: ${method} ${url}`);
return next
.handle()
.pipe(
tap(() => console.log(`Outgoing response: ${method} ${url} - ${Date.now() - now}ms`))
);
}
}
@Injectable()
: 이 클래스가 NestJS의 의존성 주입 시스템에 의해 관리되는 서비스임을 나타낸다.
intercept
메소드 : 모든 요청에 대해 가로채는 메소드이다.
context.switchToHttp().getRequest()
: 현재 HTTP 요청 객체를 가져온다.next.handle().pipe(tap(...))
: 요청을 처리한 후 응답 시간을 로그로 기록한다.import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
@Injectable()
export class TransformInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
return next.handle().pipe(
map(data => {
// 응답 데이터를 변환하는 로직을 작성한다.
return {
statusCode: context.switchToHttp().getResponse().statusCode,
data
};
})
);
}
}
@Injectable()
: 이 클래스가 NestJS의 의존성 주입 시스템에 의해 관리되는 서비스임을 나타낸다.
intercept 메소드
: 모든 요청에 대해 가로채는 메소드이다.
next.handle().pipe(map(...))
: 요청을 처리한 후 응답 데이터를 변환한다.map(data => {...})
: 응답 데이터에 추가적인 메타데이터(여기서는 statusCode)를 추가한다.import { Injectable, NestInterceptor, ExecutionContext, CallHandler } from '@nestjs/common';
import { Observable, of } from 'rxjs';
import { tap } from 'rxjs/operators';
const cache = new Map();
@Injectable()
export class CacheInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const key = `${request.method}-${request.url}`;
if (cache.has(key)) {
return of(cache.get(key));
}
return next.handle().pipe(
tap(response => cache.set(key, response))
);
}
}
@Injectable()
: 이 클래스가 NestJS의 의존성 주입 시스템에 의해 관리되는 서비스임을 나타낸다.
intercept 메소드
: 모든 요청에 대해 가로채는 메소드이다.
const cache = new Map()
: 캐시를 저장하기 위한 맵 객체이다.const key =
{request.url};
: 요청을 구분하기 위한 키이다.if (cache.has(key)) { return of(cache.get(key)); }
: 캐시에 데이터가 있으면 해당 데이터를 반환한다.import { Injectable, NestInterceptor, ExecutionContext, CallHandler, ForbiddenException } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthInterceptor implements NestInterceptor {
intercept(context: ExecutionContext, next: CallHandler): Observable<any> {
const request = context.switchToHttp().getRequest();
const user = request.user;
if (!user || !user.hasAccess) {
throw new ForbiddenException('You do not have access to this resource');
}
return next.handle();
}
}
@Injectable()
: 이 클래스가 NestJS의 의존성 주입 시스템에 의해 관리되는 서비스임을 나타낸다.
intercept 메소드
: 모든 요청에 대해 가로채는 메소드이다.
// src/app.controller.ts
import { Controller, Get, UseInterceptors } from '@nestjs/common';
import { LoggingInterceptor } from './logging.interceptor';
import { TransformInterceptor } from './transform.interceptor';
import { CacheInterceptor } from './cache.interceptor';
import { AuthInterceptor } from './auth.interceptor';
@Controller('example')
@UseInterceptors(LoggingInterceptor, TransformInterceptor, CacheInterceptor, AuthInterceptor)
export class AppController {
@Get()
findAll() {
return { message: 'Hello World' };
}
}
각각의 컨트롤러 별로 사용하려면 @UesIntercaeptors
를 통해서 각각 인터셉터들을 설정해준다.
만약에 각각 컨트롤러가 아닌 전체적으로 적용을 하고 싶다면 main.ts
파일을 수정해준다.
// src/main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { LoggingInterceptor } from './logging.interceptor';
import { TransformInterceptor } from './transform.interceptor';
import { CacheInterceptor } from './cache.interceptor';
import { AuthInterceptor } from './auth.interceptor';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.useGlobalInterceptors(
new LoggingInterceptor(),
new TransformInterceptor(),
new CacheInterceptor(),
new AuthInterceptor()
);
await app.listen(3000);
}
bootstrap();
권한 검사 인터셉터가 제대로 동작하려면 요청에 사용자 객체를 추가해야 한다. 이를 위해 간단한 미들웨어를 사용한다.
// src/user.middleware.ts
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class UserMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
req.user = { hasAccess: true }; // 또는 실제 사용자 데이터를 설정
next();
}
}
이 미들웨러를 모듈에 적용한다.
// src/app.module.ts
import { Module, MiddlewareConsumer, RequestMethod } from '@nestjs/common';
import { AppController } from './app.controller';
import { UserMiddleware } from './user.middleware';
@Module({
imports: [],
controllers: [AppController],
providers: [],
})
export class AppModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(UserMiddleware)
.forRoutes({ path: '*', method: RequestMethod.ALL });
}
}