[TypeORM + NestJS] 해시태그 추가 api 구현
게시글 등록 시 해시태그를 함께 추가할 경우,
내용은 post 테이블, 해시태그들은 hashtag 테이블에 추가할 예정
관계 설정 (ManyToMany)
하나의 게시글은 여러 개의 해시태그를 가질 수 있고, 하나의 해시태그 또한 여러 개의 게시글에 등록될 수 있으므로
둘은 다대다 관계.
// hashtag.entity.ts
@IsArray()
@ManyToMany((type) => Post, (post) => post.hashtags)
@JoinTable({
name: 'hashtag_post'
})
posts: Post[]
// post.entity.ts
@IsArray()
@ManyToMany((type) => Hashtag, (hashtag) => hashtag.posts)
hashtags: Hashtag[]
- @ManyToMany 데코레이터를 이용해 관계 설정
- hashtag.entity 쪽에서 @JoinTable을 추가하여 연결 테이블 생성 (테이블 이름은 내가 별도로 지정함)
테스트 데이터
로직을 짜보자!
게시글 좋아요/취소 토글 api 를 구현했을 때를 생각해 보면..
게시글과 유저도 ManyToMany 관계였지만, 추가와 동시에 연결테이블에 각각의 id 값이 들어가는 구조가 아니었다.
즉, post와 user는 이미 추가되어 있는 상태,
유저가 좋아요/좋아요 취소를 했을 때 연결테이블에만 데이터가 추가, 삭제되는 구조였다.
하지만 이 경우에는 해시태그 등록과 동시에 그 해시태그 ID가 게시글 ID와 함께 연결테이블에 추가되어야 한다.
배열로 받은 해시태그 데이터를 한 번에 DB에 insert
typeORM 공식 사이트에 Query Builder를 이용한 Insert 방법이 아래와 같이 나와 있다. (링크)
// 방법 1
await dataSource
.createQueryBuilder()
.insert()
.into(User)
.values([
{ firstName: "Timber", lastName: "Saw" },
{ firstName: "Phantom", lastName: "Lancer" },
])
.execute()
// 방법 2
await dataSource
.createQueryBuilder()
.insert()
.into(User)
.values({
firstName: "Timber",
lastName: () => "CONCAT('S', 'A', 'W')",
})
.execute()
하나의 게시글을 등록할 때 해시태그는 여러 개를 등록할 수 있으므로 배열로 데이터를 받을 예정이다.
즉, 데이터를 받아 여러 개의 row가 한 번에 insert 되어야 한다.
async/await 를 사용하기 위해 for ...of 반복문을 이용했다.
(이전에 foreach 를 썼더니, 함수라서 async/awiat 스코프에 영향이 있었던 경험이 있다.)
// hashtag.service.ts (나머지 부분 생략)
async createHashtag(postId: number, keywords: string[]): Promise<boolean> {
const post = await this.postService.getPostById(postId)
if(!post) {
throw new NotFoundException('존재하지 않는 게시글입니다.')
}
// 반복문 사용하여 여러 개 해시태그 row를 DB에 한 번에 insert
for(let keyword of keywords) {
await this.hashtagRepository
.createQueryBuilder()
.insert()
.into(Hashtag)
.values({ keyword })
.execute()
}
return true
}
아래와 같이 추가되었다~
연결테이블에 해시태그 ID와 게시글 ID 추가
같은 해시태그가 이미 존재할 경우와 아닌 경우를 분기하였다.
// hashtag.service.ts (나머지 부분 생략)
async createHashtag(postId: number, keywords: string[]): Promise<boolean> {
const post = await this.postService.getPostById(postId)
if(!post) {
throw new NotFoundException('존재하지 않는 게시글입니다.')
}
for(let keyword of keywords) {
// 해시태그 테이블 조회
let hashtagId = await this.hashtagRepository.findOne({
select: ['id'],
where: { keyword }
})
await this.hashtagRepository
.createQueryBuilder()
.insert()
.into(Hashtag)
.values({ keyword })
.execute()
// 해시태그 테이블 조회 => 없을 경우에만 추가
if(!hashtagId) {
const insertResult = await this.hashtagRepository
.createQueryBuilder()
.insert()
.into(Hashtag)
.values({ keyword })
.execute()
hashtagId = insertResult.identifiers[0].id
}
// 연결테이블에 각각의 id 추가
await this.hashtagRepository
.createQueryBuilder()
.insert()
.into('hashtag_post')
.values({ hashtagId, postId })
.execute()
}
return true
}
- 해시태그 테이블에 해당 키워드가 존재하는지 먼저 조회
- 없을 경우 해시태그 테이블에 추가한 후 결과값을 받아 id를 hashtagId에 대입
-> 이것 때문에 let 키워드 사용!
- 없을 경우 해시태그 테이블에 추가한 후 결과값을 받아 id를 hashtagId에 대입
- 연결 테이블(hashtag_post)에 기존의, 혹은 추가된 해시태그 아이디와 게시글 아이디를 추가
★ 연결 테이블에 이미 존재할 경우 에러 처리를 어떻게 해야할까?
const hashtagPost = await this.hashtagRepository
.createQueryBuilder()
.select('hashtag_post')
.from('hashtag_post', 'hashtag_post')
.where({ hashtagId, postId })
.getOne()
console.log(hashtagPost) // null
이렇게 작성했더니 콘솔에 계속 null 만 찍힌다..