NestJS-Relations

jaegeunsong97·2024년 1월 20일
0

NestJS

목록 보기
10/37
post-custom-banner

🖊️SQL Relations

Relational Database는 관계형 데이터베이스입니다.

One to Many(1 : N)

Many to One(N : 1)

Many to Many(N : N)

Posts에는 column으로 id, author, title, content, likeCount, commentCount가 있습니다.

1, newjeans_official, 뉴진스 민지, 메이크업 고치고 있는 민지, 1000, 1000

만약 author_password, author_thumbnail을 추가한다면 새롭게 column을 만들어야 합니다.

id, author, author_password, author_thumbnail, title, content, likeCount, commentCount

1, newjeans_official, 123456, thumbnail.png, 뉴진스 민지, 메이크업 고치고 있는 민지, 1000, 1000

그러면 우리는 Posts를 생성할 때마다 author에 관해서 중복해서 계속 넣어야하는 데이터 중복의 문제가 있습니다. 따라서 User 테이블을 생성합니다.

Posts: id, author, title, content, likeCount, commentCount
User: id, name, author_password, author_thumbnail

그리고 Posts와 User 사이의 관계를 만들면 끝납니다.

Posts: id, author, title, content, likeCount, commentCount + user_id(Foreign Key)

이를 통해서 Relation을 알아보겠습니다.

1명의 User는 N개의 Posts를 작성할 수 있습니다. 반대로 N개의 Posts는 1명의 User에게 귀속됩니다.

하지만 Posts와 Tag의 관계를 생각하면 1개의 Post에는 N개의 Tag가 붙고, 1개의 Tag는 N개의 Posts에 붙습니다.


🖊️UserModel 생성

Posts에 column으로 author가 있습니다. 앞서 말한 데이터의 중복 문제를 해결하기 위해 User 테이블을 만들고 Foreign Key를 사용합시다. 먼저 users module을 생성합니다.

nest g resource
  • app.module.ts
imports: [
    PostsModule,
    TypeOrmModule.forRoot({
      type: 'postgres',
      host: '127.0.0.1',
      port: 5432,
      username: 'postgres',
      password: 'postgres',
      database: 'postgres',
      entities: [
        PostsModel,
      ],
      synchronize: true,
    }),
    UsersModule // 자동으로 추가가 된다.
  ],
  • users/entities/users.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

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

     @Column()
     nickname: string;

     @Column()
     email: string;

     @Column()
     password: string;
}

그리고 UsersModel 엔티티를 app.module.ts에 등록해줍니다.

entities: [
  	// 데이터베이스와 연동될 Model
  	PostsModel,
  	UsersModule,
],

🖊️Column, enum

enum을 생성하기 위해서 따로 폴더를 생성하고 파일을 만들자.

  • users/const/roles.const.ts
export enum RolesEnum {
     ADMIN = 'ADMIN',
     USER = 'USER',
}
  • users/entities/users.entity.ts
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";
import { RolesEnum } from "../const/roles.const";

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

     @Column({
          length: 20, // 길이가 20넘으면 안됨
          unique: true, // 유일무이한 값이 될 것
     })
     nickname: string;

     @Column({
          unique: true,
     })
     email: string;

     @Column()
     password: string;

     @Column({
          enum: Object.values(RolesEnum), // RolesEnum에 있는 모든 value들을 가지고 사용할 것이다.
          default: RolesEnum.USER,
     })
     role: RolesEnum;
}

🖊️User관련 Service, Controller 기능

Relation 생성하기 전에 먼저 Users의 Service와 Controller를 만들자. users.module.ts에 레포지토리를 등록하자.

  • users.module.ts
import { Module } from '@nestjs/common';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { TypeOrmModule } from '@nestjs/typeorm';
import { UsersModel } from './entities/users.entity';

@Module({
  imports: [
    TypeOrmModule.forFeature([
      UsersModel,
    ])
  ],
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

UsersModel의 레포지토리를 Inject받을 부분은 Service 부분입니다.

  • users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersModel } from './entities/users.entity';
import { Repository } from 'typeorm';

@Injectable()
export class UsersService {

     constructor(
          @InjectRepository(UsersModel)
          private readonly usersRepository: Repository<UsersModel>,
     ) {}

     async createUser(nickname: string, email: string, password: string) {
          const user = this.usersRepository.create({
               nickname,
               email,
               password,
          });
          const newUser = await this.usersRepository.save(user);
          return newUser;
     }

     async getAllUsers() {
          return await this.usersRepository.find();
     }
}
  • users.controller.ts
import { Body, Controller, Get, Post } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {

  constructor(
    private readonly usersService: UsersService
  ) {}

  @Get()
  getUsers() {
    return this.usersService.getAllUsers();
  }

  @Post()
  postUser(
    @Body('nickname') nickname: string,
    @Body('email') email: string,
    @Body('password') password: string,
  ) {
    return this.usersService.createUser(nickname, email, password);
  }
}

🖊️Author Relation 생성

참조가 되는 파라미터 이름을 넣어줘야 합니다. 즉, UsersModel에서는 PostsModel의 author로 참조를 걸어야하고, 반대는 posts에 걸어야 합니다.

  • users/entities/users.entity.ts
import { Column, Entity, OneToMany, PrimaryGeneratedColumn } from "typeorm";
import { RolesEnum } from "../const/roles.const";
import { PostsModel } from "src/posts/entities/posts.entity";

@Entity()
export class UsersModel {

     @PrimaryGeneratedColumn()
     id: number;

     @Column({
          length: 20, // 길이가 20넘으면 안됨
          unique: true, // 유일무이한 값이 될 것
     })
     nickname: string;

     @Column({
          unique: true,
     })
     email: string;

     @Column()
     password: string;

     @Column({
          enum: Object.values(RolesEnum), // RolesEnum에 있는 모든 value들을 가지고 사용할 것이다.
          default: RolesEnum.USER,
     })
     role: RolesEnum;

     @OneToMany(() => PostsModel, (post) => post.author)
     posts: PostsModel[];
}
  • posts/entities/posts.entity.ts
import { UsersModel } from "src/users/entities/users.entity";
import { Column, Entity, ManyToOne, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class PostsModel { // TypeORM이 app.module.ts에 해당 정보들을 기반으로 DB에 생성한다.

     @PrimaryGeneratedColumn() // PK 이면서 자동증가전략
     id: number;

     // 1) UsersModel과 연동, Foreign Key
     // 2) Not null
     @ManyToOne(() => UsersModel, (user) => user.posts, {
          nullable: false,
     })
     author: UsersModel;

     @Column()
     title: string;

     @Column()
     content: string;

     @Column()
     likeCount: number;

     @Column()
     commentCount: number;
}

Error

실행을 하게되면 에러가 발생합니다.

async createPost(author: string, title: string, content: string) {
    const post = this.postsRepository.create({
        author, // 이 부분에서 에러
        title,
        content,
        likeCount: 0,
        commentCount: 0,
    });
    const newPost = await this.postsRepository.save(post);
    return newPost;
}

변경...

async createPost(authorId: number, title: string, content: string) {
    const post = this.postsRepository.create({
        author: {
          	id: authorId,
        },
        title,
        content,
        likeCount: 0,
        commentCount: 0,
    });
    const newPost = await this.postsRepository.save(post);
    return newPost;
}
async updatePost(postId: number, author: string, title: string, content: string) {
    const post = await this.postsRepository.findOne({
        where: {
          	id: postId,
        },
    });
    if (!post) throw new NotFoundException();
    if (author) post.author = author; // 에러: 이 부분은 필요 없음 Post에서 author를 바꿀 필요가 없기 때문에!!
    if (title) post.title = title;
    if (content) post.content = content;

    const newPost = await this.postsRepository.save(post);
    return newPost;
}

변경...

async updatePost(postId: number, author: string, title: string, content: string) {
    const post = await this.postsRepository.findOne({
        where: {
          	id: postId,
        },
    });
    if (!post) throw new NotFoundException();
    if (title) post.title = title;
    if (content) post.content = content;

    const newPost = await this.postsRepository.save(post);
    return newPost;
}

최종 코드

  • posts.controller.ts
import { Body, Controller, Delete, Get, NotFoundException, Param, Post, Put } from '@nestjs/common';
import { PostsService } from './posts.service';


@Controller('posts')
export class PostsController {
  constructor(private readonly postsService: PostsService) {}

  @Get()
  getPosts() {
    return this.postsService.getAllPosts();
  }

  @Get(':id')
  getPost(@Param('id') id: string) { // url의 id 부분만 가져오고 변수 id에 넣는다.
    return this.postsService.getPostById(+id);
  }

  @Post()
  postPosts(
    @Body('authorId') authorId: number,
    @Body('title') title: string,
    @Body('content') content: string,
  ) {
    return this.postsService.createPost(
      authorId, title, content
    );
  }

  @Put(':id')
  putPost(
    @Param('id') id: string,
    @Body('title') title?: string,
    @Body('content') content?: string,
  ) {
    return this.postsService.updatePost(
      +id, title, content,
    );
  }

  @Delete(':id')
  deletePost(@Param('id') id: string) {
    return this.postsService.deletePost(+id);
  }
}
  • posts.service.ts
import { Injectable, NotFoundException } from '@nestjs/common';
import { Repository } from 'typeorm';
import { PostsModel } from './entities/posts.entity';
import { InjectRepository } from '@nestjs/typeorm';

/**
 * author: string
 * title: string
 * content: string
 * likeCount: number
 * commentCount: number
 */
export interface PostModel {
     id: number;
     author: string;
     title: string;
     content: string;
     likeCount: number;
     commentCount: number;
   }
   
   let posts: PostModel[] = [
     {
       id: 1,
       author: 'newjeans_minji',
       title: '뉴진스 민지',
       content: '메이크업 고치고 있는 민지',
       likeCount: 100000,
       commentCount: 999,
     },
     {
       id: 2,
       author: 'newjeans_herin',
       title: '뉴진스 헤린',
       content: '춤추고 있는 헤린',
       likeCount: 100000,
       commentCount: 999,
     },
     {
       id: 3,
       author: 'newjeans_daniel',
       title: '뉴진스 다니엘',
       content: '노래부르는 다니엘',
       likeCount: 100000,
       commentCount: 999,
     },
   ]

@Injectable()
export class PostsService {

     constructor(
          @InjectRepository(PostsModel)
          private readonly postsRepository: Repository<PostsModel>
     ) {}

     async getAllPosts() {
          return await this.postsRepository.find({
               // relations를 포함한 쿼리 작성
               relations: ['author']
          });
     }

     async getPostById(id: number) {
          const post = await this.postsRepository.findOne({
               where: {
                    id,
               },
               // relations를 포함한 쿼리 작성
               relations: [
                    'author',
               ],
          });
          if (!post) throw new NotFoundException();
          return post;
     }

     async createPost(authorId: number, title: string, content: string) {
          const post = this.postsRepository.create({
               author: {
                    id: authorId,
               },
               title,
               content,
               likeCount: 0,
               commentCount: 0,
          });
          const newPost = await this.postsRepository.save(post);
          return newPost;
     }

     async updatePost(authorId: number, title: string, content: string) {
          const post = await this.postsRepository.findOne({
               where: {
                    id: authorId,
               },
          });
          if (!post) throw new NotFoundException();
          if (title) post.title = title;
          if (content) post.content = content;

          const newPost = await this.postsRepository.save(post); 
          return newPost;
     }

     async deletePost(postId: number) {
          const post = await this.postsRepository.findOne({
               where: {
                    id: postId,
               },
          });
          if (!post) throw new NotFoundException();
          await this.postsRepository.delete(postId)
          return postId;
     }
}
  • users.controller.ts
import { Body, Controller, Get, Post } from '@nestjs/common';
import { UsersService } from './users.service';

@Controller('users')
export class UsersController {

  constructor(
    private readonly usersService: UsersService
  ) {}

  @Get()
  getUsers() {
    return this.usersService.getAllUsers();
  }

  @Post()
  postUser(
    @Body('nickname') nickname: string,
    @Body('email') email: string,
    @Body('password') password: string,
  ) {
    return this.usersService.createUser(nickname, email, password);
  }
}
  • users.service.ts
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { UsersModel } from './entities/users.entity';
import { Repository } from 'typeorm';

@Injectable()
export class UsersService {

     constructor(
          @InjectRepository(UsersModel)
          private readonly usersRepository: Repository<UsersModel>,
     ) {}

     async createUser(nickname: string, email: string, password: string) {
          const user = this.usersRepository.create({
               nickname,
               email,
               password,
          });
          const newUser = await this.usersRepository.save(user);
          return newUser;
     }

     async getAllUsers() {
          return await this.usersRepository.find();
     }
}

🖊️디버거

launch.json 추가 클릭 (Node.js)

	 "version": "0.2.0",
     "configurations": [
          {
               "type": "node",
               "request": "launch",
               "name": "Debug NestJS",
               "runtimeExecutable": "yarn",
               "runtimeArgs": [ 
                    "start:debug" // yarn start:debug
               ],
               "console": "integratedTerminal", // vscode 터미널
               "restart": true,
               "port": 9229, // NestJS 디버깅 기본 포트
               "autoAttachChildProcesses": true // 디버거를 프로세스에 붙일까?
          }
     ]

디버깅 버튼 누르고 시작

profile
블로그 이전 : https://medium.com/@jaegeunsong97
post-custom-banner

0개의 댓글