지금까지의 포스팅에서는 엔티티 자체를 json으로 바꿔서 모든 정보를 반환해줬다.
하지만 스프링을 한창 배울 때 절대로 엔티티의 정보를 전부 그대로 보여주지 말라고 했던 기억이 난다.
예를 들어 User 엔티티에 password 필드가 있으면 이건 넘기면 안되니까
그러면 어떻게 원하는 필드만 편집해서 넘길 수 있는건가
인터셉터와 데코레이터 사용
먼저 반환할 엔티티의 숨기고 싶은 필드에 다음과 같이 @Exclude 데코레이터를 추가한다.
@Entity()
export class User {
...
@Column()
@Exclude() // 여기 추가
password: string;
...
}
그 다음 이 엔티티를 반환하는 컨트롤러에서 다음과 같이 사용해주자
컨트롤러에 달아주면 모든 라우트 핸들러에 적용될 것이고, 특정 라우트 핸들러에만 달아준다면 그 경로에 들어갈 때만 적용될 것이다
@UseInterceptors(ClassSerializerInterceptor) // 여기 추가
@Controller('auth')
export class UsersController {
...
@Get()
findAllUsers(@Query('email') email: string) {
return this.usersService.find(email);
}
...
}
인터셉터는 요청이나 응답을 가로채 특정 로직을 수행한 뒤, 그 결과를 반환하는 중간 처리 모듈이다.
GPT
ClassSerializerInterceptor는 NestJS에서 응답 객체를 직렬화(serialize)할 때 사용되는 내장 인터셉터
즉, 컨트롤러에서 반환한 객체를 JSON으로 변환하는 과정에서 일부 필드를 숨기거나 가공할 수 있게 해주는 도구
뭐 그렇다고 하네
응답 DTO 사용
또 다른 방법은 응답 DTO를 따로 만들어서 사용하는 것이다.
위에서의 방법은 엔티티의 있는 내용에서만 한정해서 응답을 줄수밖에 없다는 단점이 있다.
예를 들어 User에는 id, email, password 정보밖에 없지만, 이 유저가 작성한 post의 갯수도 필요할 때, 어떻게 이 정보를 같이 가공해서 넘겨줄 것인가? 두 개 이상의 리포지토리에서 정보를 가져와서 하나의 서비스에서 그 정보를 합쳐 컨트롤러에 넘겨준 다음, 컨트롤러에서 직렬화를 수행해서 보내는 것이 가장 이상적일 것이다.
DTO 만들기
import { Expose } from 'class-transformer';
export class UserDto {
@Expose()
id:number
@Expose()
email:string
}
기존의 User 엔티티에서 id와 email만 응답으로 보내고 싶다면 이렇게 UserDto를 만들자
@Expose()는 외부에 공개할 필드이다
인터셉터 만들기
export function Serialize(dto:any) {
return UseInterceptors(new SerializeInterceptors(dto));
}
export class SerializeInterceptors implements NestInterceptor{
constructor(private dto: any) {
}
intercept(context: ExecutionContext, next: CallHandler<any>): Observable<any> {
return next.handle().pipe(
map((data:any) => {
return plainToInstance(this.dto, data, {
excludeExtraneousValues: true,
})
})
);
}
}
이 인터셉터는 직렬화에 사용될 dto를 생성자에서 인자로 받고 있다.
interceptor 메소드에서 return문 내의 map의 plainToInstance로 data (원래 보내야 할 응답, UserEntity)를 인자로 받은 dto로 커스텀해서 리턴해준다
excludeExtraneousValues를 true로 설정해 놓으면 dto에서 @Expose() 데코레이터가 달려있는 필드만 직렬화한다.
코드 맨 위에서는 export function Serialize를 통해 이 데코레이터를 단순하게 사용할 수 있도록 도와주고 있다.
컨트롤러에서 @Serialize(dto)만 달아놓으면 @UseInterceptor(new SerializeInterceptors(dto) 를 사용한 것과 같은 효과가 난다. 그냥 치환해주는거라고 보면 된다.
@Controller('auth')
@Serialize(UserDto)
export class UsersController {
...
}
이렇게 컨트롤러에서도 사용 가능하고, 라우트 핸들러에서도 사용 가능하다.
인자 타입 제한하기
지금은 any타입으로 어떤 인자이든 들어올 수 있다. 이는 @Serialize("im string")과 같은 말도 안되는 인자도 들어올수 있고, 이를 컴파일 시점에 잡아내지 못하고 런타임 시점에서 오류가 터질수 있다는 소리이다.
이를 막으려고 타입스크립트 쓰는거 아닌가?
/* 추가 */
interface ClassConstructor {
new (...args: any[]): {};
}
export function Serialize(dto:ClassConstructor) { // 수정
return UseInterceptors(new SerializeInterceptors(dto));
}
이렇게 사용하면 일단은 Class 만 인자로 들어올수 있도록 강제할 수 있다. 좀더 상세한 타입으로 강제할 수도 있다고 한다.
'NestJS' 카테고리의 다른 글
| NestJS + Qdrant 사용해보자 !! (6) | 2025.08.08 |
|---|---|
| AWS bedrock 사용기 (5) | 2025.08.05 |
| 입력 정보 검증하기 (0) | 2025.06.15 |
| TypeORM 간단 사용법 (0) | 2025.06.15 |
| 제어 역전 / 의존성 주입 (0) | 2025.06.14 |