-
Notifications
You must be signed in to change notification settings - Fork 1
[준섭] 1129(수) 개발 기록 ‐ NestJS TypeORM으로 MySQL 전문검색 구현
학습 기록 참고
TypeORM 공식문서 참고
일단, mysql 서버에 접속해 우리가 검색에 필요한 user 테이블의 nickname 컬럼에 인덱스를 생성해준다.
ALTER TABLE user ADD FULLTEXT INDEX idx_fulltext_nickname (nickname);
생성된 인덱스 확인
SHOW INDEX FROM user;
FULLTEXT로 인덱스가 설정된 모습
이제 userRepository를 이용한 service로직을 작성
// auth/auth.service.ts
import ...
@Injectable()
export class AuthService {
constructor(
@InjectRepository(User)
private readonly authRepository: Repository<User>,
private readonly jwtService: JwtService,
private readonly redisRepository: RedisRepository,
) {}
// 생략 ..
async searchUser(nickname: string): Promise<User[]> {
const users: User[] = await this.userRepository
.createQueryBuilder('user')
.where(
`MATCH (user.nickname) AGAINST (:nickname IN NATURAL LANGUAGE MODE)`,
{
nickname,
},
)
.getMany();
return users;
}
}
MySQL에서는 IN NATURAL MODE가 기본값이라 적어주지 않아도 되지만 직관적이게 보이려고 적어줌
위 코드는 파라미터로 받은 nickname변수의 값으로 nickname 컬럼을 검색
그 후 API추가
// auth/auth.controller.ts
@Get('search')
searchUser(@Query('nickname') nickname: string) {
return this.authService.searchUser(nickname);
}
이제 Postman으로 테스트를 해보려고 한다.
그 전에 미리 테스트용 유저들을 조금 추가해주었다.
이렇게 “테스트”라는 단어 관련 닉네임을 가진 예시 유저 정보를 저장해주었다.
그러고 드디어 searchUser api를 테스트해보자.
localhost:3000/auth/search?nickname=테스트
ㅎㅎ 왜 안뜨지?
혹시나 해서 쿼리에 닉네임과 같은 쿼리를 넣어서 테스트 해보았다.
localhost:3000/auth/search?nickname=testtest
그러니 결과가 나왔다.
찾아보니 **NATURAL LANGUAGE MODE
**는 입력된 검색어와 가장 일치하는 결과를 반환하기 위해 설계되었다고 한다.
그래서 일치하는 값 하나만 나오는 것
우리는 비슷한 유저들을 가져오도록 유연하게 구현하기 위해 다음과 같이 코드를 수정하였다.
async searchUser(nickname: string): Promise<User[]> {
const users: User[] = await this.userRepository
.createQueryBuilder('user')
.select(['user.id', 'user.nickname'])
.where(`MATCH (user.nickname) AGAINST (:nickname IN BOOLEAN MODE)`, {
nickname: '*' + nickname + '*',
})
.getMany();
return users;
}
위처럼 IN BOOLEAN MODE
를 사용해 좀 더 유연한 검색을 하도록 했다.
*nickname*
으로 nickname 단어로 시작하는 닉네임들을 가져오도록 하였다.
이렇게 수정하고 테스트를 해보니 다음과 같이 결과가 잘 출력되었다!
localhost:3000/auth/search?nickname=테스트
NestJS는 서버가 실행될 때 자동으로 @Entity()
데코레이터가 붙은 엔티티들을 데이터베이스에 테이블로 만들어준다.
그러나 이 과정에서 생성해놓은 인덱스는 사라진다.
그래서 위 과정에서 테스트할 때에는 코드를 수정하여 서버를 다시 실행할 때 마다 아래 명령어로 다시 인덱스를 부여해주었다.
ALTER TABLE user ADD FULLTEXT INDEX idx_fulltext_nickname (nickname);
그러나 실제 서버에서도 그럴 수는 없기에 서버가 실행되면 자동으로 FULLTEXT 인덱스를 nickname 컬럼에 부여하도록 user.entity.ts에 데코레이터를 추가하였다.
import ...
@Entity()
// Index 데코레이터 추가. fulltext 옵션 true.
@Index('IDX_FULLTEXT_NICKNAME', ['nickname'], { fulltext: true })
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ type: 'varchar', length: 50, nullable: false, unique: true })
username: string;
@Column({ type: 'varchar', length: 100, nullable: false })
password: string;
@Column({ type: 'varchar', length: 50, nullable: false, unique: true })
nickname: string;
@CreateDateColumn()
created_at: Date;
@OneToMany(() => Board, (board) => board.user, { eager: false })
boards: Board[];
}
이렇게 데코레이터를 추가해서 fulltext: true 옵션을 주면 서버가 재실행되어 새로 갱신이 되더라도 FULLTEXT 인덱스가 nickname 컬럼에 대하여 자동으로 생성이 된다!
© 2023 debussysanjang
- 🐙 [가은] Three.js와의 설레는 첫만남
- 🐙 [가은] JS로 자전과 공전을 구현할 수 있다고?
- ⚽️ [준섭] NestJS 강의 정리본
- 🐧 [동민] R3F Material 간단 정리
- 👾 [재하] 만들면서 배우는 NestJS 기초
- 👾 [재하] GitHub Actions을 이용한 자동 배포
- ⚽️ [준섭] 테스트 코드 작성 이유
- ⚽️ [준섭] TypeScript의 type? interface?
- 🐙 [가은] 우리 팀이 Zustand를 쓰는 이유
- 👾 [재하] NestJS, TDD로 개발하기
- 👾 [재하] AWS와 NCP의 주요 서비스
- 🐰 [백범] Emotion 선택시 고려사항
- 🐧 [동민] Yarn berry로 모노레포 구성하기
- 🐧 [동민] Vite, 왜 쓰는거지?
- ⚽️ [준섭] 동시성 제어
- 👾 [재하] NestJS에 Swagger 적용하기
- 🐙 [가은] 너와의 추억을 우주의 별로 띄울게
- 🐧 [동민] React로 멋진 3D 은하 만들기(feat. R3F)
- ⚽️ [준섭] NGINX 설정
- 👾 [재하] Transaction (트랜잭션)
- 👾 [재하] SSH 보안: Key Forwarding, Tunneling, 포트 변경
- ⚽️ [준섭] MySQL의 검색 - LIKE, FULLTEXT SEARCH(전문검색)
- 👾 [재하] Kubernetes 기초(minikube), docker image 최적화(멀티스테이징)
- 👾 [재하] NestJS, 유닛 테스트 각종 mocking, e2e 테스트 폼데이터 및 파일첨부
- 2주차(화) - git, monorepo, yarn berry, TDD
- 2주차(수) - TDD, e2e 테스트
- 2주차(목) - git merge, TDD
- 2주차(일) - NCP 배포환경 구성, MySQL, nginx, docker, docker-compose
- 3주차(화) - Redis, Multer 파일 업로드, Validation
- 3주차(수) - AES 암복호화, TypeORM Entity Relation
- 3주차(목) - NCP Object Storage, HTTPS, GitHub Actions
- 3주차(토) - Sharp(이미지 최적화)
- 3주차(일) - MongoDB
- 4주차(화) - 플랫폼 종속성 문제 해결(Sharp), 쿼리 최적화
- 4주차(수) - 코드 개선, 트랜잭션 제어
- 4주차(목) - 트랜잭션 제어
- 4주차(일) - docker 이미지 최적화
- 5주차(화) - 어드민 페이지(전체 글, 시스템 정보)
- 5주차(목) - 감정분석 API, e2e 테스트
- 5주차(토) - 유닛 테스트(+ mocking), e2e 테스트(+ 파일 첨부)
- 6주차(화) - ERD
- 2주차(화) - auth, board 모듈 생성 및 테스트 코드 환경 설정
- 2주차(목) - Board, Auth 테스트 코드 작성 및 API 완성
- 3주차(월) - Redis 연결 후 RedisRepository 작성
- 3주차(화) - SignUpUserDto에 ClassValidator 적용
- 3주차(화) - SignIn시 RefreshToken 발급 및 Redis에 저장
- 3주차(화) - 커스텀 AuthGuard 작성
- 3주차(수) - SignOut시 토큰 제거
- 3주차(수) - 깃헙 로그인 구현
- 3주차(토) - OAuth 코드 통합 및 재사용
- 4주차(수) - NestJS + TypeORM으로 MySQL 전문검색 구현
- 4주차(목) - NestJS Interceptor와 로거
- [전체] 10/12(목)
- [전체] 10/15(일)
- [전체] 10/30(월)
- [FE] 11/01(수)~11/03(금)
- [전체] 11/06(월)
- [전체] 11/07(화)
- [전체] 11/09(목)
- [전체] 11/11(토)
- [전체] 11/13(월)
- [BE] 11/14(화)
- [BE] 11/15(수)
- [FE] 11/16(목)
- [FE] 11/19(일)
- [BE] 11/19(일)
- [FE] 11/20(월)
- [BE] 11/20(월)
- [BE] 11/27(월)
- [FE] 12/04(월)
- [BE] 12/04(월)
- [FE] 12/09(금)
- [전체] 12/10(일)
- [FE] 12/11(월)
- [전체] 12/11(월)
- [전체] 12/12(화)