본문 바로가기
Back-end/기초부터 따라하는 nest.js

#5. 기초부터 따라하는 Nest.js : 간단한 DB구조 설계 및 TypeORM을 이용한 Entity 작성

by hsloth 2023. 3. 23.

 
해당 포스팅은 nest.js 9.0.0 버전, typeorm 0.3.x 버전을 기준으로 작성되었습니다.
모든 글은 작성자의 주관이 100% 담겨있기 때문에 부정확할 수 있습니다.
 

#pre. 터미널을 켜고 프로젝트 폴더로 이동

https://suloth.tistory.com/44

 

#0-1. 기초부터 따라하는 nest.js : 터미널 키는 법 + 터미널에서 작업 폴더 이동

윈도우 윈도우는 윈도우+R 버튼을 누른 후, cmd 를 입력하여 터미널을 킵니다. 혹은 윈도우 버튼을 눌러서 검색창에 cmd를 검색하면 터미널이 나올텐데 그걸 실행시켜주시면 됩니다. Mac OS Mac의 경

suloth.tistory.com

 
위의 링크의 내용을 참고하여 study 폴더로 이동해줍니다.
그리고 code . 명령어를 통해 vscode를 열어줍니다.


이전 포스팅에서 TypeORM과 nest.js 프로젝트를 연동을 해보았습니다.
https://suloth.tistory.com/55

 

#4. 기초부터 따라하는 Nest.js : TypeORM과 DB연동

해당 포스팅은 nest.js 9.0.0 버전, typeorm 0.3.x 버전을 기준으로 작성되었습니다. 모든 글은 작성자의 주관이 100% 담겨있기 때문에 부정확할 수 있습니다. #pre. 터미널을 켜고 프로젝트 폴더로 이동 ht

suloth.tistory.com

 
이제는 DB구조를 간단히 설계해보도록 하겠습니다.
단순히 유저, 게시글, 댓글 테이블을 설계하고 TypeORM을 이용하여 DB에 테이블을 생성해 보겠습니다.
 


DB 구조 설계


DB 구조를 설계하기 위해서 그림을 그려야 합니다.
그림 그리는 툴은 원하는 걸로 사용하시면 됩니다. (저의 경우는 starUML을 사용하고 있습니다. 다만, 처음에는 배우기 어려우니 그냥 ppt같은 툴을 쓰도록 합시다)
아니면 mysql workbench를 사용하셔도 무방합니다. MySQL workbench로 생각보다 많은 것을 할 수 있으니 잘 검색해보시기 바랍니다. (윈도우의 경우 mysql을 설치하면 기본으로 깔리고, 다른 os의 경우 따로 설치를 해주어야 하는 것으로 알고 있습니다)
 

User Table

일단, User Table부터 설계해봅시다. (모르겠으면 그냥 한 번 읽어보시기 바랍니다)
일단 기본적으로 index, email, password 칼럼이 필요합니다. 그리고 유저의 생성일자, 수정일자, 삭제일자를 저정할 created_at, updated_at, deleted_at 칼럼이 필요합니다.
따라서, 이렇게 그림을 그릴 수 있습니다.

id는 pk로 설정해주고 타입은 bigint 타입으로 설정합니다. int타입이 아닌 bigint 타입으로 설정하는 이유는 최대 허용 범위때문입니다. 서비스가 커지면(이건 듀토리얼이라 그럴일은 없지만... 설명을 위해) int로 감당이 안되는 경우가 생기기 때문에 bigint로 설정하도록 합시다.
email과 password는 varchar 타입으로 설정합니다.
created_at, updated_at, deleted_at 칼럼은 timestamp 타입으로 설정합니다.
 
MySQL 공부 키워드

더보기

MySQL을 아예 모르신다면, 공부할 때 구글링 키워드입니다.

- MySQL 테이블 만들기

- MySQL 네이밍 컨벤션

- MySQL INTEGER 타입

- MySQL VARCHAR 타입

- MySQL TIMESTAMP 타입

- MySQL PK란

- MySQL 외래키란

- MySQL 관계

 

Article Table

게시글 테이블을 설계해보도록 합시다.
기본적으로 id(pk), title, content 칼럼이 필요합니다. 그리고 게시글의 생성,수정,삭제 일자를 위해 created_at, updated_at, deleted_at 칼럼이 필요합니다.
또한, 유저가 게시글을 쓰기 때문에 게시글에는 유저에 대한 정보. 즉, 유저의 id값이 있어야 합니다.
따라서 이렇게 그림을 그릴 수 있습니다.
 

여기서 content는 TEXT타입으로 설정하였습니다. (사진에는 VARCHAR인데 TEXT로 설정해주세요)
 

Comment Table

댓글 테이블을 설계해보도록 합시다.
기본적으로 id(pk), content 칼럼이 필요합니다. 그리고 댓글의 생성, 수정, 삭제 일자를 위해 created_at, updated_at, deleted_at 칼럼이 필요합니다.
또한, 대댓글 기능을 구현할 생각이라면 parent_id 칼럼을 만들어 따로 부모댓글의 id를 저장하도록 합시다.
그리고 댓글에는 유저의 정보와 게시글에 대한 정보가 담겨야 합니다. 그래야 해당 댓글의 어느 게시글의 댓글인지, 어느 유저의 댓글인지 알 수 있습니다. 따라서 user_id 칼럼과 article_id 칼럼을 외래키로 가지고 있어야 합니다.
따라서 이렇게 그림을 그릴 수 있습니다.
 

여기서 content는 TEXT타입으로 설정하였습니다. (사진에는 VARCHAR인데 TEXT로 설정해주세요)
 

테이블간의 관계

다음은 테이블간의 관계를 설정해주어야 합니다. 게시글에는 유저 id가 필요하고, 댓글또한 게시글 id와 유저 id가 필요하니... 서로 관계가 있으므로 관계를 설정해주어야 합니다.
 
유저 - 게시글 간의 관계
유저 1명이 여러 게시글을 쓸 수 있으므로 1:N 관계입니다. (유저 1 : 게시글 N)
반면, 한 게시글을 유저 여러 명이 같이 쓸 수는 없죠.
 
유저 - 댓글 간의 관계
유저 1명이 여러 댓글을 쓸 수 있으므로 1:N 관계입니다. (유저 1 : 댓글 N)
반면, 한 댓글을 유저 여러 명이 같이 쓸 수는 없습니다.
 
게시글 - 댓글 간의 관계
게시글 1개에 댓글이 여러 댓글이 달릴 수 있으므로 1:N 관계입니다. (게시글 1 : 댓글 N)
반면, 한 댓글이 여러 게시글에 공유될 수는 없습니다. 
 
따라서 최종적으로 이렇게 그림을 그릴 수 있습니다.
 

 

deleted_at 칼럼은 왜 만든거죠? 왜 굳이 삭제 날짜를 deleted_at 칼럼에 넣는건가요? 그냥 삭제하면 되는거 아닌가요?

deleted_at 칼럼은 소프트딜리트(soft delete, 논리삭제)를 위해서 만들어두는 칼럼입니다.
데이터를 삭제하지 않고 보관해야 하는 경우 사용합니다. (법적으로 데이터를 일정기간 가지고 있어야 할 때, 데이터를 활용할 일이 있을 때, 추후에 복원될 가능성이 있는 데이터 등) - 저는 대부분 softDelete를 사용합니다.
그리고 실제로 데이터베이스에서 row를 직접 삭제(hard delete, 물리삭제)를 하게 되면 추후에 데이터베이스의 인덱스를 정리할 일이 많아져 불편할 수 있다 라고도 알고 있습니다. (이 문장에 대해서는 정확히는 모르겠습니다)
 


DB 구조를 설계할 때 주의할 점은, Typescript로 테이블을 설계할 수 있다고 하더라도, MySQL로도 테이블을 만들 줄 알아야한다는 것입니다. TypeORM을 너무 신용하지 마세요. SQL 쿼리문으로 모든 걸 할 줄 알아야합니다.
 


TypeORM으로 테이블 만들기


모르겠으면 일단 그냥 따라쳐보세요. 그리고 나중에 한 번 봐보면서 구글링하면서 이해하려고 해보세요.
 
테이블 설계도를 그렸으니, 이제 TypeORM으로 테이블을 만들어줄 차례입니다.
우선, study 폴더의 src 폴더 안에 entities 폴더를 만들어보도록 합시다.
그 후, entities 폴더에 user.entity.ts / article.entity.ts / comment.entity.ts / common.entity.ts 를 만들어 보도록 하겠습니다.
각각 user 테이블 / article 테이블 / comment 테이블 을 만드는데 사용됩니다.
 

 

Decorator

데코레이터를 먼저 알아야합니다. 이전 포스팅에서도 @Module 이러식으로 @가 들어간 문법들을 많이 사용했습니다.
@가 들어간 문장을 데코레이터라고 하는데, 데코레이터는 바로 밑줄의 문장을 꾸며줍니다.
예를 들어

@Controller()
export class UserController {
...
}

위의 코드에서 @Controller() 라는 데코레이터는 아래 class UserController를 꾸며주는데, 이는 UserController가 컨트롤러라는 것을 명시해주고 해당 클래스에 컨트롤러의 역할을 부여해줍니다.
 
typeORM을 사용하면서도 많은 데코레이터들이 나올텐데, 해당 클래스, 변수, 함수에 역할을 부여한다고 생각하면 됩니다.
 

Common Entity

일단, User, Article, Comment Entity에 공통적으로 들어가는 id, created_at, updated_at, deleted_at을 가진 CommonEntity를 만들어줍니다.
상속을 통해 해당 칼럼들을 다른 Entity에 적용할 수 있습니다.
 

 
@PrimaryGeneratedColumn은 Auto Increment가 걸린 PK(기본키)를 만드는 칼럼입니다.
바로 아래줄의 id: string을 꾸며주며, auto increment가 적용된 pk인 id 라는 칼럼이라고 생각하면 됩니다.
 
@CreateDateColumn은 생성일자를 적용해주는 칼럼입니다. create 되었을 때 자동으로 생성일자를 넣어줍니다.
바로 아래줄의 createdAt: Date를 꾸며주며, createdAt 칼럼이라고 생각하면 됩니다.
 
@UpdateDateColumn은 update 쿼리를 날릴 때, 자동으로 수정일자를 넣어줍니다.
바로 아래줄의 updatedAt: Date를 꾸며주며, updatedAt 칼럼이라고 생각하면 됩니다.
 
@DeleteDateColumn은 Soft Delete를 위한 칼럼을 말합니다. typeorm의 softDelete함수를 사용해서 삭제날자를 넣어줍니다.
바로 아래줄의 deletedAt: Date를 꾸며주며, deletedAt 칼럼이라고 생각하면 됩니다.
 
 

User Entity

아까 그려놓은 그림을 보면서 작업합시다.
 

 
@Entity('User') 솔직히 필요없긴한데, 테이블 이름을 User로 설정해주는 데코레이터입니다. 이걸 적용 안할시, 클래스 이름인 UserEntity가 테이블 명이 됩니다. UserEntity 클래스를 꾸며줍니다.
 
@Column('varchar', { unique: true, nullable: false}) 일반적인 칼럼을 말합니다. 해당 칼럼의 타입은 varchar이며, unique하지 않고, null이 들어갈 수 없는 칼럼임을 가리킵니다.
바로 아래줄의 email: string을 꾸며주며, email은 유일해야하고 값이 없을 수 없기 때문에 이렇게 설정하였습니다.
 
이하 나머지 Column데코레이터의 설명은 생략하겠습니다.
 
@OneToMany(() => ArticleEntity, (article) => article.user) 일대다(1:N)관계를 의미합니다. User와 Article은 그림에서와 같이 1:N의 관계이기 때문에 TypeORM이 둘의 관계를 알려면 이렇게 설정해주어야 합니다.
마찬가지로 ArticleEntity에서도 @ManyToOne 데코레이터를 이용하여 Article과 User의 관계가 N:1 이라고 알려주어야 합니다.
바로 아래줄의 articles: ArticleEntity[] 를 꾸며주는데, articles의 타입은 ArticleEntity의 배열 타입이어야 합니다. 1:N 관계이기 때문에 Article을 배열 형식으로 받습니다.
예를들어 1번 유저에 대한 정보를 가져올 때, 어떤 게시글을 작성했는지 가져오려면, article이라는 속성이 배열속성이어야 해당 유저가 쓴 게시글을 여러개 가져올 수 있기 때문입니다. 배열이 아닌 그냥 ArticleEntity 타입이었다면 하나만 가져오고 말겠죠.
이걸 해석하려고 하면 어렵습니다. 일단 따라 치는걸 추천드립니다. 대충 해석하면,
@OneToMany(() => 관계를 맺은 테이블 class, (관계를 맺은 테이블의 별칭/즉, 아무거나 내가 원하는 변수명 입력) => 별칭.관계를 맺은 테이블 class에서 현재 Entity의 별칭/변수명)
라고 정의할 수 있습니다. 잘 모르시겠으면 그냥 위의 예를 보고 따라 치세요 ㅋㅋ
 
참고로 저는 배열타입의 변수명은 s를 붙여 복수형으로 정의합니다. 나중에 사용할 때 헷갈리지 않아서 편합니다!
 

Article Entity

 

 
@ManyToOne(() => UserEntity, (user) => user.articles) 다대일(N:1) 관계를 의미합니다. Article과 User는 그림에서와 같이 N:1의 관계이기 때문에 TypeORM이 둘의 관계를 알려면 이렇게 설정해주어야 합니다.
마찬가지고 UserEntity에서도 @OneToMany 데코레이터를 이용하여 User와 Article의 관계가 1:N이라고 알려주어야 합니다.
아래줄의 user: UserEntity를 꾸며줍니다. 여기서는 타입이 배열이 아닙니다.
 
@JoinColumn({ name: 'userId', referencedColumnName: 'id' }) 는 userId라는 이름의 칼럼이 외래키라는 것을 나타냅니다. 다른 속성 다 필요없이 그냥 @JoinColumn() 이렇게만 써줘도 될겁니다. 저는 헷갈리지 않기 위해 name과 referencedColumnName까지 적어줍니다.
JoinColumn은 관계를 맺은 테이블 중에 한쪽에만 작성해주면 됩니다. 저는 보통 외래키가 있는 쪽에 작성해줍니다.
@JoinColumn({ name: 현재Entity에서 외래키 변수명, referencedColumnName: 관계를 맺은 테이블에서 외래키 변수명 })
 
 

Comment Entity

 
자세한 건 위에서 다 설명하였으니, 생략하겠습니다.
 
TypeORM으로 테이블 생성시 주의할 점
1. 칼럼명을 변수명으로 대체하기 때문에, createdAt 같이 변수명은 camel case이지만, 실제 DB에서 칼럼명을 확인하면 created_at과 같이 snake case로 적용됩니다.
2. @Entity 데코레이터를 붙이지 않으면 class이름대로 테이블명이 결정됩니다.
 
 


TypeORM에서 작성한 Entity들을 DB의 테이블에 반영하기


 
자, 이제 Entity를 만들었습니다! 그러면, 이 Entity들이 data-source.ts 의 entities 속성에 잘 import되었는지 확인하고
터미널에서 npm run schema:sync 를 진행합시다.
 

 

터미널 마지막에 Schema synchronization finished successfully.
라는 문구가 뜨면 성공입니다.

 

+추가

여기서 맥북은 잘 되는데 윈도우는 문제가 발생하더라구요. 

문제 발생

1. path.join으로 entity를 가져올 시, entity파일들이 읽히지 않아서 DB에 테이블이 생성이 되지 않는다.

이 경우에는 path.join을 버리고, Entity를 쌩으로 박으면 됩니다.

export const dataSource = new DataSource({
  ...
  entities: [UserEntity, ArticleEntity, CommentEntity],
  ...
});

 

2. UserEntity, ArticleEntity, CommentEntity를 찾을 수 없다고 나온다.

이 경우에는 Entity들의 경로를 상대경로로 지정해주면 해결이 됩니다.

import { DataSource } from 'typeorm';
import { UserEntity } from './src/entities/user.entity';

export const dataSource = new DataSource({
  ...
  entities: [UserEntity],
});

자세히 보시면 import { UserEntity } from './src/entities/user.entity'; 부분이 바뀌었습니다.

from 이후의 문장을 상대경로로 바꿔주세요 './src/entities/user.entity';

상대경로에 대해 잘 모르신다면 구글에서 검색해서 찾아보시기 바랍니다.

 


잠깐! Github에 소스코드를 Push하기 전에!

해당 글을 꼭 읽어보고, push하시기 바랍니다. Localhost라 큰 상관은 없지만, 주의해서 나쁠건 없습니다.

https://suloth.tistory.com/65

 

#6. 기초부터 따라하는 Nest.js : dotenv와 ConfigModule을 이용한 환경변수 관리

해당 포스팅은 nest.js 9.0.0 버전, typeorm 0.3.x 버전을 기준으로 작성되었습니다. 모든 글은 작성자의 주관이 100% 담겨있기 때문에 부정확할 수 있습니다. #pre. 터미널을 켜고 프로젝트 폴더로 이동 ht

suloth.tistory.com


 

이제 밑의 글을 참고하여 github에 코드를 push 합시다.
 
https://suloth.tistory.com/46

 

#0-2. 기초부터 따라하는 Nest.js : Git과 Github 사용법

이 글은 아래의 포스팅에 이어서 작성하는 포스팅입니다. https://suloth.tistory.com/45 #1. 기초부터 따라하는 Nest.js : Nest.js 초기 설정 Nest.js란? node.js의 백엔드 프레임워크 중 하나입니다. node.js에는 수

suloth.tistory.com

 
 
참고하시라고 올려두는 사진.