라이브러리, 프레임워크/TypeORM

[TypeORM + NestJS] 해시태그 추가 api 구현

dv_jamie 2022. 6. 14. 19:09

게시글 등록 시 해시태그를 함께 추가할 경우,
내용은 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[]
  1. @ManyToMany 데코레이터를 이용해 관계 설정
  2. 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
}
  1. 해시태그 테이블에 해당 키워드가 존재하는지 먼저 조회
    1. 없을 경우 해시태그 테이블에 추가한 후 결과값을 받아 id를 hashtagId에 대입
      -> 이것 때문에 let 키워드 사용!
  2. 연결 테이블(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 만 찍힌다..