개발/개발로그

[React] 성냥퍼즐 웹 서비스 만들기 11일차 (Mysql / Prisma / NestJS)

seungho-dev 2024. 12. 17. 19:50

오늘은 지난 시간에 이어 공식문서를 보면서 nestJS에서 Prisma와 Mysql를 설치하고 연결해보려고 한다.

 

개인적인 공부를 위해 작성하는 블로그입니다. 혹시라도 잘못되거나 부족한 부분이 있다면 댓글로 알려주시면 감사하겠습니다.

 


👨🏻‍💻 Mysql 설치

Mysql은 대표적인 관계형 db 관리 시스템으로 세계에서 가장 널리 사용되는 오픈소스 데이터베이스 중 하나이다.

https://www.codeit.kr/tutorials/5/MySQL-%EC%84%A4%EC%B9%98-macOS

공식 홈페이지에서 받아도 괜찮고 brew로 받아주어도 괜찮다.

brew install mysql

 

설치가 완료되면 서버를 시작해 보자

brew services start mysql

 

다음 명령어를 실행하면 서버가 정상적으로 실행 중인지 확인할 수 있다.

brew services list

 

mysql_secure_installation를 터미널에 입력해 패스워드를 설정해 주고 다음 명령어로 클라이언트에 접속할 수 있다.

mysql -u root

 

 

💁🏻 Prisma 설치

Prisma는 TypeScript와 Node.js 환경에서 사용되는 오픈소스 ORM(Object-Relational Mapping)이다.  데이터베이스와 애플리케이션 간의 상호작용을 쉽게 관리할 수 있도록 돕는 도구로, SQL을 직접 작성하는 대신 타입 안전한 방식으로 데이터를 다룰 수 있다.

$ npm install prisma --save-dev

 

prisma가 설치되었다면 prizma cli를 사용해서 접두어 npx를 붙이고(권장) init으로 초기세팅을 해준다.

npx prisma init
  • schema.prisma: 데이터베이스 연결을 지정하고 데이터베이스 스키마를 포함함
  • .env: 일반적으로 환경 변수 그룹에 데이터베이스 자격 증명을 저장하는 데 사용됨

그럼 이렇게 prizma 폴더에 shema.prizma라는 파일이 생겼을 텐데 이곳에 들어가서 사용할 db에 대한 정보를 입력해 준다.

 

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

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

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

그리고. env 파일 DATABASE_URL에 Mysql에서 설정했던 username, password와 연결할 db 이름을 넣어준다.

# Environment variables declared in this file are automatically made available to Prisma.
# See the documentation for more detail: https://pris.ly/d/prisma-schema#accessing-environment-variables-from-the-schema

# Prisma supports the native connection string format for PostgreSQL, MySQL, SQLite, SQL Server, MongoDB and CockroachDB.
# See the documentation for all the connection string options: https://pris.ly/d/connection-strings

DATABASE_URL="mysql://root:1234@127.0.0.1:3306/myTestDB"

#mysql://<username>:<password>@<host>:<port>/<database_name>

 

👩🏻‍🚀  prisma schema model 정의

schema.prisma에 다음과 같이 Puzzle, User, Feedback, Comment의 schema를 작성해 주었다.

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

// Looking for ways to speed up your queries, or scale easily with your serverless or edge functions?
// Try Prisma Accelerate: https://pris.ly/cli/accelerate-init

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

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

model Puzzle {
  id  Int  @id @default(autoincrement())
  title String
  gameType String // "remove" || "move"
  limit Int
  difficulty String @default("Unrated")// "Easy", "Normal", "Hard", "Extreme"
  initialState Json // 초기 상태
  solution Json // 솔루션 (TODO: 분리하거나 암호화)
  category Json // 문자열 배열 저장
  createBy String // 만든 유저 아이디
  likes Int @default(0) // 좋아요 수
  attemptedCount Int @default(0) // 시도한 유저 수
  correctCount Int @default(0) // 정답을 맞힌 유저 수
  totalFeedbackScore Int @default(0) // 피드백 점수 총합
  feedbackCount Int @default(0) // 피드백을 남긴 유저 수
  createAt DateTime @default(now()) // 생성 시간 자동 기록
  feadbacks Feedback[]
  comments Comment[]

  solvedByUsers User[] @relation("SolvedPuzzles")
  attemptedByUsers User[] @relation("AttemptedPuzzles")
}

model Feedback {
  id  Int @id @default(autoincrement())
  userId Int
  puzzleId Int
  score Int // 피드백 점수(1 ~ 10)
  createAt DateTime @default(now())

  user User @relation(fields: [userId], references: [id])
  puzzle Puzzle @relation(fields: [puzzleId], references: [id])
}
model User {
  id Int @id @default(autoincrement())
  username String @unique
  email String @unique // 이메일 (로그인용)
  password String // 해싱된 패스워드
  level Int @default(1) // 유저 레벨
  solvedPuzzles Puzzle[] @relation("SolvedPuzzles")
  attemptedPuzzles Puzzle[] @relation("AttemptedPuzzles")
  feedbacks Feedback[]
  comments Comment[]
}

model Comment {
  id Int @id @default(autoincrement())
  content String // 댓글 내용
  createdAt DateTime @default(now()) // 작성 시간
  updatedAt DateTime @updatedAt

  userId Int
  puzzleId Int

  user User @relation(fields: [userId], references: [id])
  puzzle Puzzle @relation(fields: [puzzleId], references: [id])
}

 

스키마를 Mysql db에 반영하려면 다음과 같은 명령어를 입력해 주면 된다.

npx prisma migrate dev --name init
npx prisma generate

그럼 이렇게 눈 깜짝할 사이에 SQL 쿼리문으로 만들어진걸 볼 수 있다. 확인 해보려면 다음 명령어를 쉘창에 입력해보자

npx prisma studio

 

@prisma/client를 받아주면 다음과 같이 PrismaClient를 가져와 사용할 수 있다.

$ npm install @prisma/client

 

prisma client : Prisma를 통해 데이터베이스와 상호작용할 수 있는 클라이언트 라이브러리, 데이터 조회, 생성, 업데이트, 삭제 작업을 Type-safe하고 간편하게 수행할 수 있다.

import { PrismaClient } from '@prisma/client';

const prisma = new PrismaClient();

 

데이터 조회(Read)

// 모든 레코드 조회: findMany
const users = await prisma.user.findMany();
console.log(users);
// 조건에 맞는 레코드 조회: findMany + where
const activeUsers = await prisma.user.findMany({
	where: { isActive: true },
})
console.log(activeUsers);
// 단일 레코드 조회: findUnique
const user = await prisma.user.findUnique({
	where: { id: 1 },
});
console.log(user);

 

데이터 생성(Create)

// 단일 레코드 생성
const newUser = await prisma.usercreate({
data: {
	username: "seungho",
    email: "123@naver.com",
    password: "hashed_password",
    level: 1,
  },
});

 

데이터 수정(Update)

// 단일 레코드 수정
const updatedUser = await prisma.user.update({
	where: { id: 1 },
    data: { level: 2},
})

 

데이터 삭제(Delete)

// 단일 레코드 삭제
const deletedUsesr = await prisma.user.delete({
	where: { id: 1 },
})

마지막으로 prisma.service.ts 파일을 만들고 모듈 providers에 등록시켜준다. NestJS에서는 Java의 Bean과 유사하게 데이터베이스 커넥션이나 서비스 객체를 @Injectable() 데코레이터를 통해 Ioc 컨테이너에 등록하고 싱글톤 객체로 관리된다.

싱글톤(Singleton)은 소프트웨어 디자인 패턴 중 하나로, 클래스의 인스턴스를 오직 하나만 생성하고 이를 전역적으로 접근할 수 있도록 보장하는 패턴이다.
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
// OnModuleInit()를 통해 NestJS 수명 주기와 prisma를 연결해준다.
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }
}
import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { PuzzlesModule } from './puzzles/puzzles.module';
import { PrismaService } from './prisma.service';
@Module({
  imports: [PuzzlesModule],
  controllers: [AppController],
  providers: [AppService, PrismaService],
})
export class AppModule {}