본문 바로가기

Backend/NestJS

[NestJS] Entity간 관계 형성

728x90
계속 진행하던 게시판 프로젝트의
게시물과 유저의 관계 형성 공부 기록이다.

개요

entity 간의 관계를 형성하기 전에 어떤 관계인지 판단해야 한다.

게시물과 유저는 어떤 관계를 가지고 있을까?

 

게시물을 작성한 유저는 유일하지만, 유저는 많은 게시물을 작성할 수 있다.

따라서 1:M의 관계를 가진다.

typeorm 공식 문서에서는 여러 관계에 대한 데커레이터 상세 설명을 볼 수 있다.

 

리소스들이 가질 수 있는 여러 관계에 대해서도 따로 기록할 예정이다.


1:M 관계

유저의 입장에서는 유저 한 명에 게시물 여러 개,

게시물 입장에서는 게시물 여러 개에 유저 한 명이 있다.

 

관계를 형성하려면 entity에 관계를 설명하는 데커레이터와 해당 리소스를 추가해야 한다.

먼저 유저의 entity를 수정해 보자.

import { Board } from 'src/boards/board.entity';
import {
  BaseEntity,
  Column,
  Entity,
  OneToMany,
  PrimaryGeneratedColumn,
  Unique,
} from 'typeorm';

@Entity()
@Unique(['userName'])
export class User extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  userName: string;

  @Column()
  password: string;

  @OneToMany(() => Board, (board) => board.user)
  boards: Board[];
}

유저 입장에서는 1:M이므로 OneToMany 데커레이터를 사용했다.

export declare function OneToMany<T>(typeFunctionOrTarget: string | ((type?: any) => ObjectType<T>), inverseSide: string | ((object: T) => any), options?: RelationOptions): PropertyDecorator;

위 코드는 OneToMany의 정의이다.

첫 번째 인자로는 타겟 entity의 타입이다. 유저를 게시물과 연결해야 하기 때문에 Board entity를 전달했다.

두 번째 인자는 타겟 entity에서 해당 entity에 접근하는 방법이다.

뒤에서 수정하겠지만 수정할 Board entity에 user column을 추가할 예정이므로 위처럼 작성했다.

 

Board entity를 수정해 보자.

import { type } from 'os';
import { User } from 'src/auth/user.entity';
import {
  BaseEntity,
  Column,
  Entity,
  ManyToOne,
  PrimaryGeneratedColumn,
} from 'typeorm';
import { BoardStatus } from './board-status.enum';

@Entity()
export class Board extends BaseEntity {
  @PrimaryGeneratedColumn()
  id: number;

  @Column()
  title: string;

  @Column()
  description: string;

  @Column()
  status: BoardStatus;

  @ManyToOne((type) => User, (user) => user.boards)
  user: User;
}

Board 입장에서는 M:1이므로 ManyToOne 데커레이터를 사용했다.

export declare function ManyToOne<T>(typeFunctionOrTarget: string | ((type?: any) => ObjectType<T>), inverseSide?: string | ((object: T) => any), options?: RelationOptions): PropertyDecorator

위 코드는 ManyToOne의 정의이다.

첫 번째 인자로는 동일하게 타겟 entity이고,

두 번째 인자도 동일하게 타겟 entity에서 해당 entity에 접근하는 방법이다.

 

외래키

유저와 게시물의 관계를 entity 상에서는 형성해 줬다.

이제 이를 적용한 코드를 작성하려고 한다.

 

그전에 유저가 어떤 게시물을 가지고, 게시물이 어떤 유저의 소유인지 어떻게 알 수 있을까?

하나의 entity에서 관계되는 entity의 key값을 가지는 방법을 사용한다.

 

유저가 여러 개의 게시물을 가질 수 있기 때문에

게시물은 유저의 id를 key값으로 가진다.

게시물 entity에서 유저의 id를 외래키로 가지는 것이다.

게시물 작성을 하면,

위처럼 boards table에 userID가 외래키로 추가된 것을 볼 수 있다.

 

유저의 게시물 생성/조회/삭제

생성

먼저 게시물을 생성할 때 user를 추가해줘야 한다.

생성 로직을 수정해 보자.

const newBoard = this.create({
  title,
  description,
  status: BoardStatus.PUBLIC,
  user,
});

위와 같이 게시물 생성 시에 user를 추가해 줬다.

조회

이제 유저가 본인의 게시물을 조회할 수 있게 해 보자.

async getBoardsByUserId(user: User): Promise<Board[]> {
    const query = this.boardRepository.createQueryBuilder('board');
    query.where('board.userId = :userId', { userId: user.id });
    const boards = await query.getMany();
    return boards;
  }

조건에 맞는 게시물을 찾기 위해 QueryBuider를 사용했다.

Table에서 원하는 개체를 찾는 방법은 다양한데 기존에는 Repository API를 사용했다.

 

TypeORM 공식문서에서 QueryBuilderRepositoryAPI 메소드에 대한 설명과 사용법을 배울 수 있다.

삭제

유저는 본인의 게시물만 삭제할 수 있어야 한다.

따라서 삭제 로직을 수정해 보자.

  async deleteBoard(id: number, user: User) {
    const result = await this.boardRepository.delete({
      id,
      user: {
        id: user.id,
      },
    });
    if (result.affected === 0) {
      throw new NotFoundException(`There's no board with id ${id}`);
    }
  }

유저가 본인이 작성한 게시물만 삭제할 수 있도록 했다.


마치며

리소스의 관계와 Entity, TypeORM에 대한 추가적인 공부가 필요할 것 같다.

 

잘못된 정보에 대한 피드백은 환영입니다.

감사합니다.

728x90