* 정보 제공용이 아닌 개인 개발 기록용입니다! 비판 환영
1. 서론
지난 시간에는 Nest.js에서 TypeORM와 DB 연동, 그리고 실제 동작 여부를 확인하기 위한 예제 Controller 및 Service, Entity와 Repository 주입까지 해보고 실제 DB에 Insert와 Select까지 해보았다!
다 괜찮은데, 공통 Response가 있으면 좋겠다는 생각을 했다.
기존 Response는 DB에서 조회한 내용을 단순히 JSON 형태로 리턴하는 형태이다.
그런데 이런 데이터를 data라는 키로 따로 묶고, success 여부와 상태코드, 메세지를 공통 Response로 요청하면 훨씬 깔끔해보일 것 같다.
가령, 다음과 같은 형식이다.
{
success: true,
statusCode: 200,
msg: '',
data: {
id: '1',
title: 'Example Title',
content: 'Example Content'
}
}
오.. 좀 괜찮아보인다.
그러면 Controller에서 요청을 받아서 처리하고, Response를 반환할 때, 무조건 Interceptor에 걸리도록 구현해서 이 Interceptor에서 공통 Response의 형태로 변환시킨다음, 최종적으로 클라이언트에게 반환해주면 될 것이다.
자, 그럼 Nest.js 공식 도큐먼트를 참고해서 만들어보자!
Documentation | NestJS - A progressive Node.js framework
Nest is a framework for building efficient, scalable Node.js server-side applications. It uses progressive JavaScript, is built with TypeScript and combines elements of OOP (Object Oriented Progamming), FP (Functional Programming), and FRP (Functional Reac
docs.nestjs.com
2. 구현
An interceptor is a class annotated with the @Injectable() decorator,
which implements the NestInterceptor interface.
대충 읽어보면, Interceptor는 @Injectable() 데코레이터가 붙어있어야 하고, NestInterceptor Interface를 구현하면 된다고 한다.
파일부터 만들어보자.
나는 common/interceptor 안에 response.interceptor.ts로 생성하였다. 앞으로 common 안에 util 함수나, interceptor 등을 넣어놓을 예정이다.
// response.interceptor.ts
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler<any>,
): Observable<any> | Promise<Observable<any>> {
return undefined;
}
}
@Injectable() 데코레이터가 붙어있는 ResponseInterceptor라는 클래스를 만들었고, 이때 NestInterceptor Interface를 구현한다.
NestInterceptor를 Implements 하면 자동으로 IDE에 빨간 줄이 표시된다. 이때 IntelliJ 기준 Implements all members를 클릭하면, intercept가 자동으로 구현된다.
이때 context의 타입은 ExecutionContext인데, 공식 도큐먼트에 따르면 ExecutionContext
와 ArgumentsHost가 있다고 한다.
ArgumentsHost는 Request와 Response를 얻어낼 수 있는 클래스라고 하고, ExecutionContext는 ArgumentsHost를 상속받아 더 다양한 메소드를 사용할 수 있도록 만든 클래스라고 한다.
또한, ExecutionContext는 다양한 Application Context(Http-based, MicroService, WebSocket 등)에서 Request와 Response 등을 알맞은 형태로 변환시켜준다고 한다. 자세한건 공식 도큐먼트를 더 읽어봐야겠지만, 일단은 Guard와 Filter, Interceptor 등에서 자주 사용된다고 한다.. 까지만 알고 있다.
그럼 이를 활용해서 나머지를 구현해보자!
// response.interceptor.ts
import {
CallHandler,
ExecutionContext,
Injectable,
NestInterceptor,
} from '@nestjs/common';
import { map, Observable } from 'rxjs';
import { Response } from 'express';
@Injectable()
export class ResponseInterceptor implements NestInterceptor {
intercept(
context: ExecutionContext,
next: CallHandler<any>,
): Observable<any> | Promise<Observable<any>> {
// ExecutionContext로부터 Response 얻어내기
const response: Response = context.switchToHttp().getResponse();
// Response Mapping 수행
return next.handle().pipe(
map((data) => ({
success: true,
statusCode: response.statusCode,
msg: '',
data: data,
})),
);
}
}
내가 처리해준 로직은 매우 간단하다. intercept에서 전달받은 context를 통해 Response를 추출해주었다. 이때 response 변수는 express에서 제공해주는 Response 타입으로 받았다.
그리고 next.handle() 에서 pipe() 함수 내에 map() 함수로 데이터를 변환해주었는데, next.handle() 메소드는 이미 return되었던 value를 지니고 있다고 한다. 그래서 rxjs의 map 함수를 적용해줄 때 익명 함수의 인자로 바로 data를 받아 새로 정의할 객체 형태로 변환한 다음 이를 return 해준다.
사실 이 부분이 조금 헷갈리긴 했는데, 자바의 Stream과 유사해서 이해에 큰 어려움이 있진 않았다.
참, map 함수 내부에서 msg가 빈 공백 ''로 되어있는데, 이는 추후 만들 Exception Filter와 형태를 똑같이 맞춰주려고 일부러 저렇게 작성했다 ㅎㅎ..
마지막으로, 만든 Interceptor는 전역적으로 사용되어야 하므로 App Module에 등록하면 되겠다.
물론, main.ts에서 다음과 같이 지정해줄 수도 있지만,
위와 같은 방식은 Response Interceptor에서 Dependency Injection이 불가하기 때문에 나는 app.module.ts의 Provider에 적용해주었다
이제 동작을 확인해보자!
가장 먼저, 지난 시간에 만든 예제 조회 API 호출이다.
GET 방식이며, 상태값으로 200번을 반환하도록 되어있다.
처음과는 달리, success, statusCode, msg, data에 데이터가 이쁘게 담기는 것을 확인할 수 있다.
그럼 201 상태 코드를 반환하는 예제 생성 API를 호출해보자.
깔-끔
3. 다음 시간에는..?
음 다음 시간에는 Exception Filter를 한번 구현해봐야겠다. Transaction을 먼저 했어야했는데 Request Interceptor를 먼저 구현해버렸다 ㅎㅎ
Transaction은 Exception Filter를 구현한 다음에 해봐야겠다.
'좌충우돌 산악회 홈페이지 만들기' 카테고리의 다른 글
[좌충우돌 산악회 홈페이지 만들기 #5] Nest.js에 Swagger OpenAPI 붙이기 (0) | 2022.04.19 |
---|---|
[좌충우돌 산악회 홈페이지 만들기 #4] Nest.js에서 Exception Filter 만들기 (0) | 2022.04.17 |
[좌충우돌 산악회 홈페이지 만들기 #2] TypeORM을 활용한 DB 연동과 Cross-env 및 Dotenv 세팅, 예제 컨트롤러/서비스 개발! (0) | 2022.04.17 |
[좌충우돌 산악회 홈페이지 만들기 #1] 필요한 것들을 나열해보자 (0) | 2022.04.15 |
[좌충우돌 산악회 홈페이지 만들기 #0] 산악회 홈페이지를 만들자! (0) | 2022.04.15 |