JUINTINATION

Express.js와 Prisma ORM + MySQL 본문

StudyNote

Express.js와 Prisma ORM + MySQL

DEOKJAE KWON 2024. 1. 15. 22:55
반응형

현재 진행중인 스퍼트 프로젝트의 조건 중 MySQL + Prisma ORM + 3개 테이블 이상(1:N, M:N, 최소 하나) + ERD이 있었다. 이를 위해 먼저 Prisma가 무엇인지 알아보고 기본적인 CURD API를 만들어보도록 하겠다.

ORM이란?

ORM(Object Relational Mapping)은 "객체로 연결을 해준다"라는 의미로 어플리케이션과 데이터베이스 연결 시 SQL언어가 아닌 어플리케이션 개발언어로 데이터베이스를 접근할 수 있게 해주는 툴이다. 즉, 어떤 객체를 schema로 정의한 다음 server side쪽에서 데이터 베이스를 CRUD가능하도록 해주는 매개체이다. SQL문법 대신 어플리케이션의 개발언어를 그대로 사용할 수 있게 함으로써, 개발 언어의 일관성과 가독성을 높여준다는 장점을 갖고 있다.

Prisma란?

Prisma는 Node.js와 TypeScript용 ORM으로 공식 문서에 따르면 Prisma는 아래의 구성 요소로 이루어져 있다.

  • Prisma Client: Auto-generated and type-safe query builder for Node.js & TypeScript
  • Prisma Migrate: Migration system
  • Prisma Studio: GUI to view and edit data in your database

앞서 언급한 것처럼 Node.js에서 Prisma ORM을 사용해 MySQL과 연동해볼 것이다.

Prisma 및 MySQL 설치

먼저 MySQL이 설치되지 않았다면 $ brew install mysql 커맨드를 실행하여 홈브루에서 설치한다. $ brew install mysqlworkbench 커맨드를 실행하여 워크벤치도 같이 설치하면 도움이 될 것이다.

이후에 Express.js 프로젝트를 생성하고 다음의 커맨드를 순서대로 실행한다. 나는 $ express crud-test 커맨드를 실행하고 crud-test 디렉터리로 이동하여 $ npm install express 커맨드, 그리고 $ npm init -y 커맨드를 순서대로 입력하여 프로젝트를 생성하였고

이후에 $ npm install prisma @prisma/client 커맨드를 실행한 이후에 $ npx prisma init 커맨드를 실행하였다.

위의 커맨드를 모두 실행하면 프로젝트 루트 디렉토리에 다음 두 개의 파일이 생성된다.

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

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

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
  • .env
# 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="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public"

.env 파일 작성

.env 는 데이터베이스 연결을 위한 환경변수를 정의할 때 사용하는 파일이며 나는 mysql을 사용할 것이므로 MySQLWorkbench에서 테스트용 db를 create한 후에 .env 파일을 다음과 같이 수정하면 된다.

# 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://[user]:[pw]@[host]:[portNum]/[dbName]"

MySQL 계정을 [user] 대신 넣고 비밀번호를 [pw] 대신 집어넣으면 된다. 나는 로컬 호스트에서 돌릴 것이기 때문에 [host]에 localhost를 적을 것이고 다른 host에서 돌릴 것이라면 [host] 부분을 바꾸면 된다. 또한 포트 번호의 보통 기본 설정은 3306으로 되어 있지만 다른 포트 번호를 쓴다면 [portNum] 부분을 바꿔주면 되고 'prisma_test_db' 가 아닌 다른 이름으로 db를 생성했다면 [dbName] 부분을 바꿔주면 된다.

Prisma Schema 파일 작성

prisma/schema.prisma 는 Prisma 설정을 위한 메인 파일이며, 아래의 사항을 설정할 수 있다.

  • Data sources: Prisma가 연결해야하는 데이터베이스에 접속 시 필요한 정보를 정의
  • Generators: Prisma Client를 기반으로 생성되어야 하는 클라이언트를 정의
  • Data model definition: 데이터 모델(테이블)을 정의

나는 mysql을 사용할 것이기 때문에 schema.prisma의 Data sources 부분을 다음과 같이 수정했다.

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

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

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

url은 아까 .env 파일에서 입력했던 "mysql://[user]:[pw]@[host]:[portNum]/[dbName]"를 그대로 써도 되지만 위와 같이 적어도 자동으로 가져오기 때문에 저런 식으로 적는 것이 나중에 유지보수적인 측면에서 더 좋을 것이다.

Generators 부분은 따로 건드리지 않고 Data model definition 부분을 위의 링크에서 확인할 수 있는 예시 그대로 적어보았다.

model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique
  name    String?
  role    Role     @default(USER)
  posts   Post[]
  profile Profile?
}

model Profile {
  id     Int    @id @default(autoincrement())
  bio    String
  user   User   @relation(fields: [userId], references: [id])
  userId Int    @unique
}

model Post {
  id         Int        @id @default(autoincrement())
  createdAt  DateTime   @default(now())
  updatedAt  DateTime   @updatedAt
  title      String
  published  Boolean    @default(false)
  author     User       @relation(fields: [authorId], references: [id])
  authorId   Int
  categories Category[]
}

model Category {
  id    Int    @id @default(autoincrement())
  name  String
  posts Post[]
}

enum Role {
  USER
  ADMIN
}

홈페이지에서 볼 수 있는 해당 db에 있는 테이블의 관계를 나타낸 ERD는 아래와 같다.

https://www.prisma.io/docs/static/0db90251163930754d2f83b0e9ce86d3/4c573/sample-database.png

이후에 이 프로젝트가 있는 위치에서 $ npx prisma migrate dev --name [nameOfMigration] 커맨드를 실행하면 마이그래이션 파일이 생성되고 db에 적용된다.

나는 vscode에서 $ npx prisma migrate dev --name initial 커맨드를 입력하였고 아래 사진처럼 20240116105720_initial 디렉터리에 migration.sql 파일이 생성된 것을 볼 수 있다. $ npx prisma migrate deploy [nameOfMigration] 커맨드를 실행하면 원하는 마이그래이션 파일로 덮어씌울 수 있고 $ npx prisma db pull 커맨드 실행을 통해

원래는 $ npx prisma db push 커맨드를 실행하여 바로 db에 덮어씌울 수도 있지만 해당 커맨드는 마이그래이션 파일을 생성해주지 않는데 예를 들어 테이블을 수정했을 때 연관관계에 문제가 생겼을 때 이전 마이그래이션 파일이 존재하지 않는다면 처음부터 끝까지 모든 테이블을 분석하는 상황이 발생할 수도 있기 때문에 잘 쓰지 않는다.

$ npx prisma db push 커맨드를 실행하지 않았음에도 실제로 Tableplus에 해당 db를 연결해봤을 때 테이블은 제대로 생성된 것을 확인할 수 있다. 제대로 적용됐는지 확인하기 위해 app.js 를 다음과 같이 수정해보자.

const { PrismaClient } = require('@prisma/client');

const prisma = new PrismaClient();

async function main() {
  console.log(await prisma.user.findMany());
}

main();

이후에 $ node app.js 커맨드를 실행하여 확인해보면 에러없이 잘 실행되는 것을 확인할 수 있다.

그러면 schema.prisma에 Temp 라는 테이블을 추가하고 app.js 파일도 다음과 같이 추가해보자.

const { PrismaClient } = require('@prisma/client');

const prisma = new PrismaClient();

async function main() {
  console.log(await prisma.user.findMany());
  console.log(await prisma.temp.findMany());
}

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

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

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

model User {
  id      Int      @id @default(autoincrement())
  email   String   @unique
  name    String?
  role    Role     @default(USER)
  posts   Post[]
  profile Profile?
}

model Profile {
  id     Int    @id @default(autoincrement())
  bio    String
  user   User   @relation(fields: [userId], references: [id])
  userId Int    @unique
}

model Post {
  id         Int        @id @default(autoincrement())
  createdAt  DateTime   @default(now())
  updatedAt  DateTime   @updatedAt
  title      String
  published  Boolean    @default(false)
  author     User       @relation(fields: [authorId], references: [id])
  authorId   Int
  categories Category[]
}

model Category {
  id    Int    @id @default(autoincrement())
  name  String
  posts Post[]
}

model Temp {
  id    Int    @id @default(autoincrement())
}

enum Role {
  USER
  ADMIN
}

이후에 $ node app.js 커맨드를 다시 실행하여 확인해보면 2번째부터 오류가 발생하는 것을 확인할 수 있다. 그러면 $ npx prisma migrate dev --name add_temp 커맨드를 실행하여 db에 적용한 뒤에 다시 $ node app.js 커맨드를 실행해보면 정상적으로 작동하는 것을 확인할 수 있다.
추가적으로 $ npx prisma generate 커맨드는 언제 쓰이는 건지 이것저것 실험해봤는데 $ npx prisma db push 커맨드를 실행하던 $ npx prisma migrate dev --name add_temp 커맨드를 실행하던 자동으로 실행되는 것 같다. 그래서 따로 $ npx prisma generate 커맨드를 추가적으로 실행할 필요는 없는 것 같다.


결론

Prisma ORM에 대해 알아봤는데 생각보다 정보가 많지 않아서 좀 헤맸던 것 같다. $ npx prisma generate 커맨드는 정확히 어떤 역할을 하는지는 추후에 알아볼 시간이 생긴다면 더 알아보도록 하겠다. 다음 글은 아마 이 Prisma ORM으로 CRUD API를 만드는 글이 될 것이다.
스터디 그룹에서 지금까지 정리한 Node.js + Express.js 관련 글에 대한 피드백을 받았는데 그 피드백은 언제쯤 적용할 수 있을지 모르겠지만.. 시간이 날 때마다 꾸준히 업데이트해보도록 하겠다.

728x90
Comments