[코팩] NestJs Relationship 설정해보기 (typeorm)

Seong Hyeon Kim·2024년 3월 21일
0

NestJs

목록 보기
14/14

One to One Relationship (1:1) 작업해보기

src/entity/profile.entity.ts

import { Column, Entity, JoinColumn, OneToOne, PrimaryGeneratedColumn } from "typeorm";
import { UserModel } from "./user.entity";


@Entity()
export class ProfileModel{
    @PrimaryGeneratedColumn()
    id: number;
    
    @OneToOne(()=> UserModel, (user)=> user.profile)
    @JoinColumn()
    user: UserModel;

    @Column()
    profileImg: string;
}

우선 유저테이블을 기준으로 프로필사진도 넣어보기 위해서 프로필모델이 존재할 엔티티 파일을 위와같이 작성해줍니다.

이 ProfileModel 에서 실제로 벨류값으로서 가치가 있는것은 사실 profileImg 뿐이고 나머지 2개의 컬럼들은 Relation 을 위한 그리고 기본 primary키로 사용될 컬럼값인 id 입니다.

그리고 @JoinColumn()은 유저 모델에서 어떤 값을 기준으로 가져올 것인지를 명시해주기위해 반드시 필요한 부분입니다.

좀더 자세한 설명은 뒤에서 작성하겠습니다.

src/entity/user.entity.ts

  .
  .
  .
  .
  .
 
  @Column()
  @Generated('uuid')
  additionalId: string;

  @OneToOne(()=> ProfileModel, (profile)=> profile.user)
  profile: ProfileModel;
}

마찬가지로 1대1 연결이기 때문에 유저모델에서도 프로필모델과 연결된 @OneToOne 을 동일하게 사용해줘야 합니다.

이렇게 서로 연결함으로써

유저모델 -> 프로필 모델로 join 을 하게된 형태라고 볼수있습니다.


src/app.module.ts


@Module({
  imports: [
    TypeOrmModule.forFeature([
      UserModel,
      ProfileModel (추가됨)
  ]),
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: '127.0.0.1',
      port: 5432,
      username: 'postgres2',
      password: 'postgres2',
      database: 'typeormstudy',
      entities: [
        UserModel,
        StudentModel,
        TeacherModel,
        BookModel,
        CarModel,
        ComputerModel,
        AirplaneModel,
        SingleBaseModel,
        ProfileModel (추가됨)
      ],
      synchronize: true,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})

앱 모듈에서도 엔티티에 ProfileModel 추가해주고, 컨트롤러에서도 사용할 예정이기 때문에 TypeOrmModule.forFeature 에도 추가를 해줍니다.


src/app.controller.ts


@Controller()
export class AppController {
  constructor(
    @InjectRepository(UserModel)
    private readonly userRepository: Repository<UserModel>,

    @InjectRepository(ProfileModel) (추가됨)
    private readonly profileRepository: Repository<ProfileModel> (추가됨)
  ) {}


.
.
.
.
.
.


@Post('user/profile')
  async createUserAndProfile(){
    const user = await this.userRepository.save({
      email: 'asdf@naver.com',
    });

    const profile = await this.profileRepository.save({
      profileImg: 'asdf.jpg',
      user,
    })

    return user;
  }

앱 컨트롤러 상단부에는 프로필모델을 사용하기 위해 @InjectRepository(ProfileModel) 를 추가해주시고,

앱 컨트롤러 제일 하단부에 위와같이 코드를 작성해줍니다.
우선은 그냥 포스트 요청만 하면 자동으로 아이디가 한개 생성되도록 합니다.

그리고 포스트 user.entity.ts 파일에서 이전에 실험을 위해 작성한 title 관련 내용은 전부 주석처리 해줘서 새로운 데이터가 잘 생성되도록 세팅해줍니다.

그리고 post 요청에서도 사용될 email 컬럼을 간단히 추가후 마지막으로 db 파일을 삭제한다음 다시 도커를 실행해서 db 를 새롭게 만들어줍니다.


포스트맨을 실행해보면 새로운 프로필정보가 잘 생성된 것을 볼 수 있습니다.

이때 잘 생성됬는지 확인하기 위해 get 요청을 해보면서 한번 더 알 수 있지만, 생성된 유저정보에 프로필 이미지에 관한건 없습니다.

이는 get 요청을 할때에 userRepository 에 있는 값만을 가져오기 때문인데요


  @Get('users')
  getUsers() {
    return this.userRepository.find({
      relations:{
        profile: true,
      }
    });
  }

유저 레포지토리에 연결된 값중 하나인 profile 을 가져오기 위해 relations 을 추가로 작성해주고 다시 서버를 실행한후 포스트맨 요청을 해보면,
이젠 프로필 이미지까지 잘 나오는것을 볼 수 있습니다





Many to one (1:N) 사용해보기

1대N 혹은 N대1의 경우 1:1과 굉장히 유사한 형태입니다.

차이점이 있다면 1:1 은 서로 어떤칼럼을 join을 어떤테이블에 둘 수 있는지를 정했다면

1:N 은 기본적으로 N 을 상징하는 쪽에 칼럼값이 추가될 수 밖에 없는 구조입니다.

1명의 유저 명의로 여러개의 게시물을 가지고있을 수 있기 때문입니다.

src/entity/post.entity.ts

import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from "typeorm";
import { UserModel } from "./user.entity";


@Entity()
export class PostModel{
    @PrimaryGeneratedColumn()
    id: number;

    @ManyToOne(()=> UserModel, (user)=> user.posts)
    author: UserModel;

    @Column()
    title: string;
}

우선 엔티티폴더에 다음과같이 코드를 생성해줍니다.


src/entity/user.entity.ts

.
.
.
.

  @Column()
  @Generated('uuid')
  additionalId: string;

  @OneToOne(()=> ProfileModel, (profile)=> profile.user)
  profile: ProfileModel;

  @OneToMany(()=>PostModel, (post)=> post.author)
  posts: []
  

그리고 유저엔티티에서도 posts 를 추가해줍니다. 이때 post는 여러개가 될 수 있기때문에 배열로 설정해주고, 아까 포스트에서 @ManyToOne 이였으니깐 이번엔 유저쪽에서는 @OneToMany 로 설정해주면 됩니다.

@Module({
  imports: [
    TypeOrmModule.forFeature([
      UserModel,
      ProfileModel,
      PostModel -- (추가됨)
  ]),
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: '127.0.0.1',
      port: 5432,
      username: 'postgres2',
      password: 'postgres2',
      database: 'typeormstudy',
      entities: [
        UserModel,
        StudentModel,
        TeacherModel,
        BookModel,
        CarModel,
        ComputerModel,
        AirplaneModel,
        SingleBaseModel,
        ProfileModel,
        PostModel -- (추가됨)
      ],
      synchronize: true,
    }),
  ],

앱모듈에서도 엔티티에 추가해주고 db 를 새로고침해보면 잘 추가된 것을 볼 수 있습니다.

게시물이 있고, 그 게시물의 제목, 그리고 그 게시물을 작성한 사람의 id 가 있으면 되는데 정상적으로 잘 생성된 것 같네요.

src/app.controller.ts

@Controller()
export class AppController {
  constructor(
    @InjectRepository(UserModel)
    private readonly userRepository: Repository<UserModel>,

    @InjectRepository(ProfileModel)
    private readonly profileRepository: Repository<ProfileModel>,

    @InjectRepository(PostModel)
    private readonly postRepository: Repository<PostModel> --- (추가됨)
  ) {}

.
.
.
.
.
.
  @Get('users')
  getUsers() {
    return this.userRepository.find({
      relations:{
        profile: true,
        posts: true,
      }
    });
  }

.
.
.
.
.


  @Post('user/post')
  async createUserPost(){
    const user = await this.userRepository.save({
      email: "posting_test_nestuser@naver.com"
    })

    await this.postRepository.save({
      author: user,
      title: 'post_1'
    })

    await this.postRepository.save({
      author: user,
      title: 'post_2'
    })

    return  user
  }

이후 injectable 까지 완료해서 포스트모듈 잘 가져온 후 간단하게 포스팅이 되는 api 간단하게 만든후 미리 get 요청에도 추가해두면서 포스트맨으로 테스트 해보겠습니다.

포스트요청은 성공적으로 잘 되었고,

get 요청으로 확인해보니 게시물도 잘 나오는것을 볼 수 있습니다.





Many to Many (N:N) 사용해보기

이어서 다대다 연결까지 마무리 해보겠습니다.

N:N 연결로 쉽게 이해가 되는 예시로 태그를 많이 사용하기 때문에 태그까지 추가로 만들어보겠습니다.

여러명의 유저가 동일한 하나의 태그를 여러개의 게시물에 각각 따로 들어갈 수 있기 때문에 N:N 의 예시로 좋습니다.

src/entity/tag.entity.ts

import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from "typeorm";
import { UserModel } from "./user.entity";
import { PostModel } from "./post.entity";


@Entity()
export class TagModel{

    @PrimaryGeneratedColumn()
    id: number;

    @ManyToMany(()=>PostModel, (post)=>post.tags)
    posts: PostModel[];

    @Column()
    name : string;

}

먼저 태그들을 생성하기 위한 엔티티 파일을 만듭니다.

이때 태그를 사용할곳은 기본적으로 게시물에서 사용이 될것이고, 유저가 작성한 게시물에서 사용할 것이기 때문에 태그엔티티 파일은 유저모델과 포스트모델에서 @ManyToMany 를 작성해줍니다.


src/entity/post.entity.ts

@Entity()
export class PostModel{
    @PrimaryGeneratedColumn()
    id: number;

    @ManyToOne(()=> UserModel, (user)=> user.posts)
    author: UserModel;

    @ManyToMany(()=>TagModel, (tag)=>tag.posts)
    @JoinTable()
    tags : TagModel[];

    @Column()
    title: string;
}

그리고 포스트 모델에서도 @ManyToMany 를 추가로 작성해준 후 @ManyToMany 의 경우는 사용되는곳 쪽준 한곳에 @JoinTable() 을 추가로 작성해줘야 하는데 여기서는 포스트모델에 작성해보았습니다.


@Module({
  imports: [
    TypeOrmModule.forFeature([
      UserModel,
      ProfileModel,
      PostModel,
      TagModel (추가됨)
  ]),
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: '127.0.0.1',
      port: 5432,
      username: 'postgres2',
      password: 'postgres2',
      database: 'typeormstudy',
      entities: [
        UserModel,
        StudentModel,
        TeacherModel,
        BookModel,
        CarModel,
        ComputerModel,
        AirplaneModel,
        SingleBaseModel,
        ProfileModel,
        PostModel,
        TagModel (추가됨)
      ],
      synchronize: true,
    }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

당연히 app module 에도 추가를 해줍니다.


업데이트된 db를 보면 n:n 테이블과 tag 테이블이 추가된 것을 볼 수 있습니다.

src/app.controller.ts

.
.
.
.
.


  @Post('posts/tags')
  async createPostTags(){
    const post1 = await this.postRepository.save({
      title: 'Nest Js',
    });
    
    
    const post2 = await this.postRepository.save({
      title: 'Programmaing'
    });

    
    const tag1 = await this.tagRepository.save({
      name: 'Javascript',
      posts:[post1,post2],
    });


    const tag2 = await this.tagRepository.save({
      name: 'Typescript',
      posts : [post1],
    });

    const post3 = await this.postRepository.save({
      title : 'next Js',
      tags: [tag1,tag2],
    })
    
    return true;

  }


  @Get('posts')
  getPosts(){
    return this.postRepository.find({
      relations:{
        tags: true
      }
    })
  }

  @Get('tags')
  getTags(){
    return this.tagRepository.find({
      relations:{
        posts:true
      }
    })
  }

  

이제 완성된 Relationship 을 테스트해보기 위해 임시로 생성될 게시물을 하나 만드는 post 요청과 생성된 게시물을 볼 수 있도록 get 요청을 추가로 만들어 줍니다.

실행결과 태그 테이블에서도 이 테그들이 어떤 포스트들에 관계설정이 되어있는지,
마찬가지로 포스트 테이블에도 어떤 태그들이 들어가 있는지를 잘 확인할 수 있습니다

profile
삽질도 100번 하면 요령이 생긴다. 부족한 건 경험으로 채우는 백엔드 개발자

0개의 댓글