Prisma를 이용해 게시판을 개발해보자

 

[게시판 프로젝트] 시작하기

[게시판 프로젝트] 설계

게시판 프로젝트 ERD

 

위 그림을 바탕으로 각 테이블간의 관계 및 요구사항을 정리해보자.

 

게시판 프로젝트 테이블 관계 및 요구사항 정리

사용자( Users )는 1개의 사용자 정보 ( UserInfo )를 가지고 있다.

  • Users 테이블과 UserInfo 테이블은 1:1 관계를 가지고 있다.

사용자( Users )는 여러개의 게시글 ( Posts )을 등록할 수 있다.

  • Users 테이블과 Posts 테이블을 1:N 관계를 가지고 있다.

사용자( Users )는 여러개의 댓글 ( Commnets )을 작성할 수 있다.

  • Users 테이블과 Comments 테이블은 1:N 관계를 가지고 있다.

하나의 게시글 ( Posts )은 여러개의 댓글 ( Comments )이 작성될 수 있다.

  • Posts 테이블과 Comments 테이블은 1:N 관계를 가지고 있다.

 


 

Community-Hub 프로젝트 생성

[게시판 프로젝트] 라이브러리 설치

# 프로젝트를 초기화합니다.
yarn init -y

# 라이브러리를 설치합니다.
yarn add express prisma @prisma/client cookie-parser jsonwebtoken

# nodemon 라이브러리를 DevDependency로 설치합니다.
yarn add -D nodemon

# 설치한 Prisma를 초기화 하여, Prisma를 사용할 수 있는 구조를 생성합니다.
npx prisma init
  • package.json에 "type":"module"도 추가 ( ES6 문법 사용하기 위함 )

 

프로젝트 구조 ( 게시판 프로젝트 )

내 프로젝트 폴더 이름
├── .env
├── .gitignore
├── package.json
├── yarn.lock
├── prisma
│    └── schema.prisma
└── src
  └── app.js

 

SQL로 미리 확인해보기

CREATE TABLE Users
(
    userId    INTEGER             NOT NULL AUTO_INCREMENT PRIMARY KEY,
    email     VARCHAR(191) UNIQUE NOT NULL,
    password  VARCHAR(191)        NOT NULL,
    createdAt DATETIME(3)         NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    updatedAt DATETIME(3)         NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
);


CREATE TABLE UserInfos
(
    userInfoId   INTEGER        NOT NULL AUTO_INCREMENT PRIMARY KEY,
    userId       INTEGER UNIQUE NOT NULL, -- 1:1 관계 이므로 UNIQUE 조건을 삽입합니다.
    name         VARCHAR(191)   NOT NULL,
    age          INTEGER        NOT NULL,
    gender       VARCHAR(191)   NOT NULL,
    profileImage VARCHAR(191)   NULL,
    createdAt    DATETIME(3)
                                NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    updatedAt    DATETIME(3)    NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
);

ALTER TABLE UserInfos
    ADD CONSTRAINT FK_UserInfos_Users
        FOREIGN KEY (userId) REFERENCES Users (userId) ON DELETE CASCADE;

CREATE TABLE Posts
(
    postId    INTEGER      NOT NULL AUTO_INCREMENT PRIMARY KEY,
    userId    INTEGER      NOT NULL,
    title     VARCHAR(191) NOT NULL,
    content   VARCHAR(191) NOT NULL,
    createdAt DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    updatedAt DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
);

ALTER TABLE Posts
    ADD CONSTRAINT FK_Posts_Users
        FOREIGN KEY (userId) REFERENCES Users (userId) ON DELETE CASCADE;


CREATE TABLE Comments
(
    commentId INTEGER      NOT NULL AUTO_INCREMENT PRIMARY KEY,
    userId    INTEGER      NOT NULL,
    postId    INTEGER      NOT NULL,
    content   VARCHAR(191) NOT NULL,
    createdAt DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
    updatedAt DATETIME(3)  NOT NULL DEFAULT CURRENT_TIMESTAMP(3)
);

ALTER TABLE Comments
    ADD CONSTRAINT FK_Comments_Posts
        FOREIGN KEY (postId) REFERENCES Posts (postId) ON DELETE CASCADE;

ALTER TABLE Comments
    ADD CONSTRAINT FK_Comments_Users
        FOREIGN KEY (userId) REFERENCES Users (userId) ON DELETE CASCADE;

 

 

[게시판 프로젝트] Prisma 설계하기

Prisma model 구현하기

먼저, 구현될 요구사항을 바탕으로 Prisma의 모델을 작성해보자.

각 테이블의 요구사항을 바탕으로 schema.prisma 파일의 모델을 작성해보자.

 

사용자 ( Users ) 테이블

Name 타입 ( Type ) NULL default
userId ( PK ) INTEGER NOT NULL AUTO_INCREMENT
email STRING NOT NULL  
password STRING NOT NULL  
createdAt DATETIME NOT NULL 현재 시간
updatedAt DATETIME NOT NULL 현재 시간

 

게시글 ( Posts ) 테이블

Name 타입 ( Type ) NULL default
postId ( PK ) INTEGER NOT NULL AUTO_INCREMENT
title STRING NOT NULL  
content TEXT NOT NULL  
ceratedAt DATETIME NOT NULL 현재 시간
updatedAt DATETIME NOT NULL 현재 시간

 

사용자 정보 ( UsersInfos ) 테이블

Name 타입 ( Type ) NULL default
userInfoId ( PK ) INTEGER NOT NULL AUTO_INCREMENT
name STRING NOT NULL  
age INTEGER NULL  
gender STRING NOT NULL  
profileImage STRING NULL  
createdAt DATETIME NOT NULL 현재 시간
updatedAt DATETIME NOT NULL 현재 시간

 

댓글 ( Comments ) 테이블

Name 타입 ( Type ) NULL default
commentId ( PK ) INTEGER NOT NULL AUTO_INCREMENT
content STRING NOT NULL  
createdAt DATETIME NOT NULL 현재 시간
updatedAt DATETIME NOT NULL 현재 시간

 

위 작성한 테이블을 기준으로 Prisma model을 구현해보자.

schema.prisma에 Prisma mode을 입력한다.

 

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model Users{
	userId Int @id @default(autoincrement()) @map("userId")
	email String @unique @map("email")
	password String @map("password")

	createdAt DateTime @default(now()) @map("createdAt")
	updatedAt DateTime @updatedAt @map("updatedAt")

	@@map("Users")
}

model Posts{
	postId Int @id @default(autoincrement()) @map("postId")
	title String @map("title")
	content String @map("content") @db.Text

	createdAt DateTime @default(now()) @map("createdAt")
	updatedAt DateTime @updatedAt @map("updatedAt")

	@@map("Posts")
}

model UserInfos{
	userInfoId Int @id @default(autoincrement()) @map("userInfoId")
	name String @map("name")
	age Int? @map("age")
	gender String @map("gender")
	profileImage String? @map("profileImage")

	createdAt DateTime @default(now()) @map("createdAt")
	updatedAt DateTime @updatedAt @map("updatedAt")

	@@map("UserInfos")
}

model Comments{
	commentId Int @id @default(autoincrement()) @map("commentId")
	content String @map("content")
	
	createdAt DateTime @default(now()) @map("createdAt")
	updatedAt DateTime @updatedAt @map("updatedAt")

	@@map("Comments")
}

 

Prisma 1:1 관계

요구사항 중 "사용자 ( Users )는 1개의 사용자 정보 ( UserInfo )를 가지고 있다." 에서 사용자와 사용자 정보 모델의 경우 1:1 관계를 가지고 있는 것을 확인할 수 있다. 해당 모델을 비교해 Prisma model은 어떤 방법으로 관계를 설정하는지 확인해보자.

 

// schema.prisma 

model Users {
  userId    Int      @id @default(autoincrement()) @map("userId")
  email     String   @unique @map("email")
  password  String   @map("password")
  createdAt DateTime @default(now()) @map("createdAt")
  updatedAt DateTime @updatedAt @map("updatedAt")

  userInfos UserInfos? // 사용자(Users) 테이블과 사용자 정보(UserInfos) 테이블이 1:1 관계를 맺습니다.

  @@map("Users")
}


model UserInfos {
  userInfoId   Int      @id @default(autoincrement()) @map("userInfoId")
  userId       Int      @unique @map("userId") // 사용자(Users) 테이블을 참조하는 외래키
  name         String   @map("name")
  age          Int?     @map("age")
  gender       String   @map("gender")
  profileImage String?  @map("profileImage")
  createdAt    DateTime @default(now()) @map("createdAt")
  updatedAt    DateTime @updatedAt @map("updatedAt")

  // Users 테이블과 관계를 설정합니다.
  user Users @relation(fields: [userId], references: [userId], onDelete: Cascade)

  @@map("UserInfos")
}

 

사용자 ( Users ) 모델은 사용자 정보 ( UserInfos ) 모델과 1:1관계를 가지고 있다.

여기서, 1:1 관계란 한 사용자가 하나의 사용자 정보만 가질 수 있고, 한 사용자 정보는 한 사용자에게만 속할 수 있다는 것을 의미한다.

 

이런 1:1 관계를 설정할 때는 다음과 같은 내용을 포함해야 한다.

  1. 관계를 설정하려는 모델 ( UserInfos )에서 어떤 모델과 관계를 맺을지 ( Users ) 설정해야 한다.
  2. 관계를 맺게되는 모델 ( Users )에서 어떤 모델이 관계를 맺는지 ( UserInfos ) 설정해야한다.
  3. 관계를 맺게되는 모델 ( Users )에서 타입을 지정할 때, Optional Parameter( ? )를 지정해 줘야한다. ( 사용자는 사용자 정보가 존재하지 않을 수 있기 때문 )

사용자 정보 ( UserInfos ) 모델에서 설정한 부분을 자세히 살펴보자.

 ● Users

  • 일반적인 Int, String과 같은 타입이 아닌, 참조할 다른 모델을 지정한다.
  • 사용자 ( Users ) 모델을 참조하므로 Users로 작성되어있다

 ● fields

    ● 사용자 정보 ( UserInfos ) 모델에서 사용할 외래키 ( Forien Key ) 컬럼을 지정한다.

        ● 여기서는 userId 컬럼으로 외래키를 지정했다.

 ● references

    ●  key : 참조하는 다른 모델의 Column를 지정한다.

        ●  여기서는 사용자 ( Users ) 모델의 userId 컬럼을 참조한다.

● onDelete | onUpdate

    ● 참조하는 모델이 삭제 or 수정될 경우 어떤 행위를 할 지 설정한다.

    ● Cascade 옵션을 선택해 사용자가 삭제될 경우 그에 연결된 사용자 정보도 함께 삭제되도록 설정했다.

 

Prisma 1:N 연관 관계

요구사항 중 "사용자 ( Users )는 여러개의 게시글 ( Posts )을 등록할 수 있다." 에서 사용자와 게시글 모델의 경우 1:N 관계를 가지고 있는 것을 확인할 수 있다. 이번에도 2가지의 모델의 Prisma model에서 어떻게 관계를 설정하는지 확인해보자.

 

먼저 게시글 ( Posts ) model에서 관계를 설정하는 부분을 살펴보자.

// schema.prisma

model Users {
  userId    Int      @id @default(autoincrement()) @map("userId")
  email     String   @unique @map("email")
  password  String   @map("password")
  createdAt DateTime @default(now()) @map("createdAt")
  updatedAt DateTime @updatedAt @map("updatedAt")

  userInfos UserInfos? // 사용자(Users) 테이블과 사용자 정보(UserInfos) 테이블이 1:1 관계를 맺습니다.
  posts     Posts[] // 사용자(Users) 테이블과 게시글(Posts) 테이블이 1:N 관계를 맺습니다.

  @@map("Users")
}

model Posts {
  postId    Int      @id @default(autoincrement()) @map("postId")
  userId    Int      @map("userId") // 사용자(Users) 테이블을 참조하는 외래키
  title     String   @map("title")
  content   String   @map("content") @db.Text
  createdAt DateTime @default(now()) @map("createdAt")
  updatedAt DateTime @updatedAt @map("updatedAt")

  // Users 테이블과 관계를 설정합니다.
  user     Users     @relation(fields: [userId], references: [userId], onDelete: Cascade)

  @@map("Posts")
}

 

사용자 ( Users ) 모델과 게시글 ( Posts ) 모델은 1:N 관계를 가지고 있다.

여기서 1:N 관계랑 한 사용자는 여러개의 게시글을 작성할 수 있다는 것을 의미한다.

 

이런 1:N 관계를 설정할 때는 다음과 같은 내용을 포함해야 한다.

  1. 관계를 설정하려는 모델 ( Posts )에서 어떤 모델과 관계를 맺을지 ( Users ) 설정해야한다.
  2. 관계를 맺게되는 모델 ( Users )에서 어떤 모델이 관계를 맺는지 ( Posts ) 설정해야한다.
  3. 관계를 맺게되는 모델 ( Users )에서 타입을 지정할 때, 배열 연산자 ( [] )를 작성해줘야한다. ( 사용자는 여러개의 게시글을 가질 수 있기 때문 )

현재 게시글 모델의 경우 작성한 사용자가 회원 탈퇴 ( onDelete )하게 될 경우 작성한 모든 게시글이 삭제되도록 구현되어 있다. 이런 설정은 @relation 어노테이션을 사용해 지정한다.

// Users 테이블과 관계를 설정합니다.
user     Users     @relation(fields: [userId], references: [userId], onDelete: Cascade)

 

여기서 User는 게시글 ( Posts )이 참조하는 다른 모델을 지정하고, fields는 게시글 ( Posts ) 모델에서 사용할 외래키 컬럼을 지정한다. references는 참조하는 다른 모델의 컬럼을 지정하고, onDelete는 참조하는 모델이 삭제될 경우 어떤 행위를 할 지 설정한다.

 

onDelete의 경우, Cascade 옵션으로 사용자가 삭제될 경우 연관된 게시글 또한 삭제되도록 설정했다.

 

댓글(Comments) 또한, 게시글 ( Posts )과 마찬가지로 사용자 ( Users ) 모델과 1:N 관계를 가지고 있다.

댓글 ( Comments ) model에서 관계를 설정하는 부분을 살펴보자.

model Users {
  userId    Int      @id @default(autoincrement()) @map("userId")
  email     String   @unique @map("email")
  password  String   @map("password")
  createdAt DateTime @default(now()) @map("createdAt")
  updatedAt DateTime @updatedAt @map("updatedAt")

  userInfos UserInfos? // 사용자(Users) 테이블과 사용자 정보(UserInfos) 테이블이 1:1 관계를 맺습니다.
  posts     Posts[] // 사용자(Users) 테이블과 게시글(Posts) 테이블이 1:N 관계를 맺습니다.
  comments  Comments[] // 사용자(Users) 테이블과 댓글(Comments) 테이블이 1:N 관계를 맺습니다.

  @@map("Users")
}

model Posts {
  postId    Int      @id @default(autoincrement()) @map("postId")
  userId    Int      @map("userId") // 사용자(Users) 테이블을 참조하는 외래키
  title     String   @map("title")
  content   String   @map("content") @db.Text
  createdAt DateTime @default(now()) @map("createdAt")
  updatedAt DateTime @updatedAt @map("updatedAt")

  // Users 테이블과 관계를 설정합니다.
  user     Users      @relation(fields: [userId], references: [userId], onDelete: Cascade)
  comments Comments[] // 게시글(Posts) 테이블과 댓글(Comments) 테이블이 1:N 관계를 맺습니다.

  @@map("Posts")
}

model Comments {
  commentId Int      @id @default(autoincrement()) @map("commentId")
  postId    Int      @map("postId") // 게시글(Posts) 테이블을 참조하는 외래키
  userId    Int      @map("userId") // 사용자(Users) 테이블을 참조하는 외래키
  content   String   @map("content")
  createdAt DateTime @default(now()) @map("createdAt")
  updatedAt DateTime @updatedAt @map("updatedAt")

  // Posts 테이블과 관계를 설정합니다.
  post Posts @relation(fields: [postId], references: [postId], onDelete: Cascade)
  // Users 테이블과 관계를 설정합니다.
  user Users @relation(fields: [userId], references: [userId], onDelete: Cascade)

  @@map("Comments")
}

 

게시판 프로젝트 최종 Prisma model

// schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "mysql"
  url      = env("DATABASE_URL")
}

model Users {
  userId    Int      @id @default(autoincrement()) @map("userId")
  email     String   @unique @map("email")
  password  String   @map("password")
  createdAt DateTime @default(now()) @map("createdAt")
  updatedAt DateTime @updatedAt @map("updatedAt")

  userInfos UserInfos? // 사용자(Users) 테이블과 사용자 정보(UserInfos) 테이블이 1:1 관계를 맺습니다.
  posts     Posts[] // 사용자(Users) 테이블과 게시글(Posts) 테이블이 1:N 관계를 맺습니다.
  comments  Comments[] // 사용자(Users) 테이블과 댓글(Comments) 테이블이 1:N 관계를 맺습니다.

  @@map("Users")
}

model Posts {
  postId    Int      @id @default(autoincrement()) @map("postId")
  userId    Int      @map("userId") // 사용자(Users) 테이블을 참조하는 외래키
  title     String   @map("title")
  content   String   @map("content") @db.Text
  createdAt DateTime @default(now()) @map("createdAt")
  updatedAt DateTime @updatedAt @map("updatedAt")

  // Users 테이블과 관계를 설정합니다.
  user     Users      @relation(fields: [userId], references: [userId], onDelete: Cascade)
  comments Comments[] // 게시글(Posts) 테이블과 댓글(Comments) 테이블이 1:N 관계를 맺습니다.

  @@map("Posts")
}

model UserInfos {
  userInfoId   Int      @id @default(autoincrement()) @map("userInfoId")
  userId       Int      @unique @map("userId") // 사용자(Users) 테이블을 참조하는 외래키
  name         String   @map("name")
  age          Int?     @map("age")
  gender       String   @map("gender")
  profileImage String?  @map("profileImage")
  createdAt    DateTime @default(now()) @map("createdAt")
  updatedAt    DateTime @updatedAt @map("updatedAt")

  // Users 테이블과 관계를 설정합니다.
  user Users @relation(fields: [userId], references: [userId], onDelete: Cascade)

  @@map("UserInfos")
}

model Comments {
  commentId Int      @id @default(autoincrement()) @map("commentId")
  postId    Int      @map("postId") // 게시글(Posts) 테이블을 참조하는 외래키
  userId    Int      @map("userId") // 사용자(Users) 테이블을 참조하는 외래키
  content   String   @map("content")
  createdAt DateTime @default(now()) @map("createdAt")
  updatedAt DateTime @updatedAt @map("updatedAt")

  // Posts 테이블과 관계를 설정합니다.
  post Posts @relation(fields: [postId], references: [postId], onDelete: Cascade)
  // Users 테이블과 관계를 설정합니다.
  user Users @relation(fields: [userId], references: [userId], onDelete: Cascade)

  @@map("Comments")
}

 

Prisma DB, Table 생성

.env 설정

# .env

DATABASE_URL="mysql://root:aaaa4321@express-database.qeradf.ap-northeast-2.rds.amazonaws.com:3306/community_hub"

 

자신이 대여중인 아마존 RDS EndPoint를 참조해서 경로를 설정한다.

 

아래 명령어를 입력해 db와 앞서 설계한 테이블을 생성한다.

# 해당 프로젝트에 schema.prisma에 정의된 테이블을 MySQL에 생성합니다.
npx prisma db push

 

생성 모습

 

+ Recent posts