Skip to content

[준섭] 1129(수) 개발 기록 ‐ NestJS TypeORM으로 MySQL 전문검색 구현

송준섭 edited this page Nov 29, 2023 · 6 revisions

학습 기록 참고
TypeORM 공식문서 참고

NestJS + TypeORM에서 전문검색 구현

일단, mysql 서버에 접속해 우리가 검색에 필요한 user 테이블의 nickname 컬럼에 인덱스를 생성해준다.

ALTER TABLE user ADD FULLTEXT INDEX idx_fulltext_nickname (nickname);
1

생성된 인덱스 확인

SHOW INDEX FROM user;
2

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으로 테스트를 해보려고 한다.

그 전에 미리 테스트용 유저들을 조금 추가해주었다.

3

이렇게 “테스트”라는 단어 관련 닉네임을 가진 예시 유저 정보를 저장해주었다.

그러고 드디어 searchUser api를 테스트해보자.

localhost:3000/auth/search?nickname=테스트

4

ㅎㅎ 왜 안뜨지?

혹시나 해서 쿼리에 닉네임과 같은 쿼리를 넣어서 테스트 해보았다.

localhost:3000/auth/search?nickname=testtest

5

그러니 결과가 나왔다.

찾아보니 **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=테스트

6

서버 실행시 인덱스 부여

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 컬럼에 대하여 자동으로 생성이 된다!

소개

규칙

학습 기록

[공통] 개발 기록

[재하] 개발 기록

[준섭] 개발 기록

회의록

스크럼 기록

팀 회고

개인 회고

멘토링 일지

Clone this wiki locally