안녕하세요! 오늘은 TypeORM을 사용하면서 겪었던 재미있는 이슈 하나를 공유하려고 합니다.
특히 비밀번호 해싱같은 작업을 할 때 자주 마주치는 문제인데요, 이 글을 통해 여러분의 소중한 시간을 아낄 수 있었으면 좋겠습니다! 😊
🚨 문제 상황
관리자 계정을 생성하는 API를 만들던 중이었습니다. 당연히 비밀번호는 해시화해서 저장해야 하니, Entity에 @BeforeInsert
데코레이터를 사용했죠.
@Entity('admin_user')
export class AdminUserEntity extends CommonEntity {
@Column()
userId: string;
@Column()
password: string;
@BeforeInsert()
async hashPassword() {
if (this.password) {
const salt = await bcrypt.genSalt();
this.password = await bcrypt.hash(this.password, salt);
}
}
}
서비스 로직은 이렇게 작성했습니다:
async createAdminUser(body: CreateAdminUserDto) {
await this.adminUserRepository.insert(body);
}
그런데... 🤦♂️ 데이터베이스를 확인해보니 비밀번호가 해시화되지 않고 그대로 저장되어 있었어요!
🔍 원인 파악
알고보니 TypeORM의 insert()
메서드는 Entity의 lifecycle hooks를 실행하지 않는다고 합니다.
즉, @BeforeInsert
, @AfterInsert
같은 데코레이터들이 동작하지 않는 거죠!
✨ 해결 방법
다행히도 이 문제를 해결할 수 있는 방법이 두 가지나 있습니다.
1️⃣ create()와 save() 메서드 사용하기
가장 추천하는 방법입니다. Entity의 lifecycle hooks를 그대로 활용할 수 있어요.
async createAdminUser(body: CreateAdminUserDto) {
// create()로 엔티티 인스턴스 생성
const adminUser = this.adminUserRepository.create(body);
// save()로 저장 (이 때 @BeforeInsert hook이 실행됨)
return await this.adminUserRepository.save(adminUser);
}
2️⃣ insert() 사용 시 직접 구현하기
꼭 insert()
를 사용해야 하는 경우라면, 해싱 로직을 직접 구현할 수 있습니다.
이 때는 코드 재사용성을 위해 유틸리티 클래스를 만드는 것을 추천드려요!
// utils/password.util.ts
export class PasswordUtil {
static async hash(password: string): Promise<string> {
const salt = await bcrypt.genSalt();
return bcrypt.hash(password, salt);
}
}
// service
async createAdminUser(body: CreateAdminUserDto) {
const adminUserData = {
...body,
password: await PasswordUtil.hash(body.password)
};
await this.adminUserRepository.insert(adminUserData);
}
🤔 어떤 방법을 선택해야 할까?
두 가지 방법 모두 장단점이 있습니다:
create() + save() 방식
✅ Entity의 모든 lifecycle hooks 활용 가능
✅ 데이터 검증과 처리가 일관적
❌ 단일 쿼리 대비 약간의 성능 차이
insert() + 직접 구현 방식
✅ 단순 INSERT라서 성능상 이점
✅ 로직을 명확하게 제어 가능
❌ Entity의 lifecycle hooks 활용 불가
❌ 추가 유틸리티 코드 필요
💡 결론
특별한 이유가 없다면 create()
와 save()
조합을 사용하는 것을 추천드립니다.
Entity의 lifecycle hooks를 활용할 수 있고, 코드도 더 깔끔해지니까요!
하지만 대량의 데이터를 처리하거나 성능이 특별히 중요한 경우라면,insert()
를 사용하고 필요한 로직을 직접 구현하는 것도 좋은 선택이 될 수 있습니다.
🌟 꿀팁!
Entity에서 비밀번호 해싱이 제대로 동작하는지 확인하고 싶다면,
이렇게 로그를 찍어보세요:
async createAdminUser(body: CreateAdminUserDto) {
console.log('1. 원본 비밀번호:', body.password);
const adminUser = this.adminUserRepository.create(body);
console.log('2. create() 후:', adminUser.password);
const savedUser = await this.adminUserRepository.save(adminUser);
console.log('3. save() 후:', savedUser.password);
return savedUser;
}
이렇게 하면 각 단계별로 비밀번호가 어떻게 변하는지 쉽게 확인할 수 있답니다! 😉
도움이 되셨나요? 여러분의 소중한 시간을 아낄 수 있었길 바랍니다!
더 좋은 정보로 다음에 또 찾아뵙겠습니다. 감사합니다! 👋
'개발 > NestJS' 카테고리의 다른 글
[NestJS] Queue 완벽 가이드: 기초 개념부터 실전 활용까지 (0) | 2024.11.25 |
---|---|
NestJS JWT 인증 시 발생하는 'secretOrPrivateKey must have value' 에러 완벽 해결하기 🔐 (1) | 2024.11.21 |
NestJS 설정 관리의 진화: nestjs-library-config 도입기 (4) | 2024.11.18 |
NestJS에서 AWS S3 파일 업로드 깔끔하게 구현하기 🚀 (0) | 2024.11.15 |
😎 NestJS class-transform 제대로 알고 쓰자! (3) | 2024.11.12 |
댓글