Published on

Prisma

Authors
  • avatar
    Name
    Inhwan Cho
    Twitter

Prisma

Prisma는 오픈소스 ORM(Object-relational mapping) 중 하나입니다.

ORM이란? DB데이터를 객체로 매핑해 주는 역할을 하는 것입니다.

즉, SQL문법을 사용하지 않고, 다른 언어(파이썬,자바스크립트 ...)를 사용하여 데이터베이스를 수정할 수 있도록 연결해주는(번역 역할) 서비스입니다.

사용 방법

  1. 설치
  2. 데이터베이스가 어떻게 생겼는지 schema를 작성하여 알려주기.
  3. schema를 기반으로 자동완성 사용 가능.
  4. prisma studio를 통해 관리 가능.
# 설치
npm install prisma

# 시작
npx prisma init

# prisma schema 설정하기 - 아래 글 Prisma Schema 참조
# 모델을 적용하기(db마다 다름)
npx prisma init --datasource-provider sqlite

# 만든 schema를 sqlite로 생성하기 - lite가 아닌 실제 db를 사용하는 경우 생략.
npx prisma migrate dev --name init

Prisma Schema

  • prisma 폴더에 schema.prisma라고 생성되며 크게 3가지 설정을 해줘야한다.
  • VS code를 사용하고 있다면 extension에서 prisma를 설치하면 보기 편합니다.

데이터 소스: 연결될 데이터 소스에 대한 세부사항을 명시한다.
생성자: 어떤 클라이언트가 생성되어야 하는지에 대해 설명한다.
데이터 모델: 모델을 설정한다.

Prisma 테스트해보기(설치 + 사용)

# 테스트용 폴더 만들기
 mkdir prismatest
# 폴더로 이동
 cd prismatest
# 패키지 설치 및 세팅
 npm i typescript ts-node @types/node -D
 npx tsc --init
 npm i prisma
# 만약, datasource를 다른 db로 설정 시 .env파일 및 다수의 파일 수정 필요.
# 하지만 테스트용으로 `sqlite` 생성 시 따로 설정할게 거의 없기에 `sqlite`로 생성.
 npx prisma init --datasource-provider sqlite
 code .
# VS코드에서 prisma/schema.prisma로 이동해서
# schema.prisma 설정하기
schema.prisma
//  prisma/schema.prisma
//생성자 - 어떤 클라이언트가 생성되어야 하는지에 대해 설명
generator client {
  provider = "prisma-client-js"
}

//데이터소스(db) - 연결될 데이터 소스에 대한 세부사항을 명시
datasource db {
  provider = "sqlite"
  url      = env("DATABASE_URL")
}

// 모델 작성해야 합니다.
// 데이터 모델 - 모델을 설정.
model User {
  // id,email,name,article을 가지고있는 object생성
  id Int @id @default(autoincrement())
  email String @unique
  name String?
  // articles은 Article이라는 모델(생성 필요)
  articles Article[]
}

model Article {
  id Int @id @default(autoincrement())
  title String
  body String?
  author User @relation(fields: [authorId], references: [id])
  authorId Int
}

npx prisma migrate dev --name init 으로 해당 스키마를 migate.

-- schema의 형식에 따라 생성된 파일은 다음과 같습니다.
-- prisma/migrations/202311....../miration.sql을 보시면 다음과 같은 파일이 생성되어 있습니다.
-- CreateTable
CREATE TABLE "User" (
    "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    "email" TEXT NOT NULL,
    "name" TEXT
);

-- CreateTable
CREATE TABLE "Article" (
    "id" INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
    "title" TEXT NOT NULL,
    "body" TEXT,
    "authorId" INTEGER NOT NULL,
    CONSTRAINT "Article_authorId_fkey" FOREIGN KEY ("authorId") REFERENCES "User" ("id") ON DELETE RESTRICT ON UPDATE CASCADE
);

-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");

index.ts 파일을 생성-> user를 만들고 테스트 해보기

// /index.ts
import { PrismaClient } from "@prisma/client";

const prisma = new PrismaClient();

async function main() {
  // prisma query
  // create user
  const user = await prisma.user.create({
    data :{
      name: 'inhwan',
      email: 'inhwan@gmail.com',
    },
  });
  const users = await prisma.user.findMany();
  console.log(users)
}

main()
.then(async ()=>{
  await prisma.$disconnect();
})
.catch(async (error)=>{
  console.log(error)
  await prisma.$disconnect();
  process.exit(1);
})

npx ts-node index.ts를 입력하여 콘솔창에 생성된 object가 잘 나오는지 확인해보기.

프리즈마 주요 명령어

npx prisma generate: 프리즈마 스키마에서 위에 언급된 모든 정보를 읽는다.
npx prisma migrate dev: data sources 와 data model 정의를 읽는다.
npx prisma introspect : Database의 구조를 자동으로 Prisma schema 로 불러올 수 있습니다.
npx prisma migrate : 스키마를 migrate하는 명령어입니다.
npx prisma client: Database를 변경하는 기능입니다.
npx prisma studio : GUI로 Database를 편집할 수 있는 명령어입니다.

@@index 사용 예시

schma.prisma

model User {
  id       Int      @id @default(autoincrement())
  name     String
  email    String   @unique

@@index(name: "name_aaa_index", [name, email])
}
// name과 email에 대해 index를 정의함. index 이름은 name_aaa_index
// 파라미터 순서는 상관 없음.

참고

유튜브 - prisma crash course
Prisma Docs

schema.prisma
phone Int  == Int 타입으로 필수로 할당되어야함.
phone Int? == phone 있다면 Int 타입으로 할당 되어야 하지만 값이 없을 수도 있음.
@id        == 아이디(식별자 코드) 값 - 유니크함
@unique    == 유니크한 값 - 식별자 코드는 아님(이메일 주소 같은 값)
@default   == 설정을 안했을 때의 기본 값
autoincrement() == 따로 설정을 하지 않으면 값이 1씩 증가해서 할당
now()      == 생성되었을 때의 시간을 할당
@relation  == (fields)는 현재 모델에서 관계있는 값, (references)는 현재 모델과 관계있는 값
              (onDelete)는 관계있는 데이터가 삭제됬을때 같이 삭제할것인지 여부.
              만약, relation fields가 동일한 2개가 있는 경우 (name)을 입력해줘야합니다.
@updatedAt == 업데이트가 될때마다 변경.
@@index    == 데이터베이스 인덱스를 정의할 때 사용
  • 동일한 모델의 값을 참조하는 경우
schema.prisma
// 만약, relation fields가 동일한 2개가 있는 경우 name을 입력
model User {
  id         Int         @id @default(autoincrement())
  phone      String?     @unique
  reviewsBy  Review[]    @relation(name: "writtenReviews")
  reviewsFor Review[]    @relation(name: "receivedReviews")
}

model Review {
  id           Int      @id @default(autoincrement())
  createdBy    User     @relation(name: "writtenReviews", fields: [createdById], references: [id], onDelete: Cascade)
  createdFor   User     @relation(name: "receivedReviews", fields: [createdForId], references: [id], onDelete: Cascade)
  review       String   @db.MediumText
  createdById  Int
  createdForId Int

  @@index([createdById])
  @@index([createdForId])
}
  • 동일한 값을 가진 모델을 생성하고 싶은 경우
  • enum을 사용. - String과 유사하나 오타를 방지하여 선택권을 강제함.
schema.prisma
// 또한, 동일한 모델을 생성하고 싶은 경우 'enum'을 사용하면 좋습니다.
// 여기서는 'Purchase'와 'Sale'의 동일한 내용의 모델을 생성하는 겁니다.
model User {
  ...
  records  Review[]
}

model Record {
  id        Int      @id @default(autoincrement())
  user      User     @relation(fields: [userId], references: [id])
  product   Product  @relation(fields: [productId], references: [id])
  userId    Int
  createAt  DateTime @default(now())
  updateAt  DateTime @updatedAt
  productId Int
  kind      Kind

  @@index([userId])
  @@index([productId])
}

enum Kind {
  Purchase
  Sale
}

  • 추후 작업하다가 데이터베이스에서 required 옵션에 대한 충돌이 날 경우 아래와 같은 방법으로 해결.
  1. 기존 데이터 베이스를 지운 후 생성
  2. 새로 추가되는 필드를 옵셔널하게 생성(ex. String? Int?)
  3. default value를 입력하여 기존 데이터에도 추가되도록 설정(ex. default(5))
  • 기존의 데이터를 초기화 하고 싶을 경우(전체 데이터 제거)
$ npx prisma migrate reset
$ npx prisma db push

@@index

model Product {
user      User     @relation(fields: [userId], references: [id])
...

@@index([userId])
}

db와 분리된 데이터 구조입니다. 계속해서 흔적과 위치를 추적합니다.

예를들어, userId 1인 유저가 있다면 새로운 상품을 등록하면 db에도 등록이 되고, index도 업데이트가 됩니다. 그 후 Id 1 유저를 조회하면 db 전체를 조회하는 대신 index를 조회하여 시간과 비용을 줄일 수 있습니다.