Open rimo030 opened 3 weeks ago
공식문서를 따라 마이그레이션을 시도합니다.
루트에서 아래 명령어를 입력합니다.
npm install prisma --save-dev
프리즈마는 schema.prisma
라는 파일에 DB 커넥션 정보와 모델을 정의합니다.
아래 명령어로 루트에 prisma
디렉토리를 생성하고 하위에 schema.prisma
을 추가할 수 있습니다.
npx prisma init
init 명령어로 생성된 schema.prisma
는 기본적으로 아래와 같이 커넥션 정보를 담고 있습니다.
저는 mysql
을 사용할 것이라 provider
를 변경해 주었습니다.
// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema
datasource db {
provider = "mysql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
.env
파일에는 아래와 같이 커넥션 문자열을 정의합니다.
DATABASE_URL="데이터베이스종류://계정이름:비밀번호@호스트:포트/데이터베이스명"
예를들어 다음과 같은 데이터 베이스 연결정보가 있다면,
{
"type": "mysql",
"host": "localhost",
"port": 3306,
"username": "myUser",
"password": "myPassword",
"database": "commerce"
}
이렇게 작성하면 됩니다.
DATABASE_URL="mysql://myUser:myPassword@localhost:3306/commerce"
env("DATABASE_URL")
는 프로젝트에 전역적으로 ConfigModule이 설정되어있다면, 정상적으로 작동할 것입니다.
아래는 제 프로젝트에 설정된 ConfigModule
입니다.
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { AuthModule } from './auth/auth.module';
@Module({
imports: [
AuthModule,
ConfigModule.forRoot({
cache: true,
isGlobal: true,
}),
],
})
export class AppModule {}
현재 우리의 DB에는 TypeORM의 Entity를 바탕으로 생성된 테이블들이 존재합니다.
이를 프리즈마가 읽어 모델로 변환해 줍니다. 이를 pull
이라고 합니다. 아래 명령어를 입력합니다.
npx prisma db pull
schema.prisma
를 확인해보면 추가된 모델들을 확인할 수 있습니다.
저는 다음과 같은 내용이 추가되어있습니다.
이제 pull
받은 내용을 컨벤션에 맞게 수정합니다.
@map
이나 @@map
을 사용합니다. ✌️VSCode를 사용하신다면, 수정전 Prisma 익스텐션을 설치하는 것을 추천드립니다.
예시로 프로젝트의 product
모델을 가져왔습니다.
조정전의 모델입니다.
model product {
id Int @id @default(autoincrement())
created_at DateTime @default(now()) @db.DateTime(6)
updated_at DateTime @default(now()) @db.DateTime(6)
deleted_at DateTime? @db.DateTime(6)
seller_id Int
bundle_id Int?
category_id Int
company_id Int
is_sale Int @db.TinyInt
name String @db.VarChar(128)
description String? @db.VarChar(255)
delivery_type String @db.VarChar(128)
delivery_free_over Int?
delivery_charge Int
img String @db.VarChar(255)
cart cart[]
order_product order_product[]
category category @relation(fields: [category_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_0dce9bc93c2d2c399982d04bef1")
seller seller @relation(fields: [seller_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_79a3ae0442388a2418ec67a3120")
company company @relation(fields: [company_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_a0503db1630a5b8a4d7deabd556")
product_bundle product_bundle? @relation(fields: [bundle_id], references: [id], onDelete: NoAction, onUpdate: NoAction, map: "FK_f35662270f8bcb3f26dfd6e9fda")
product_option product_option[]
product_required_option product_required_option[]
@@index([category_id], map: "FK_0dce9bc93c2d2c399982d04bef1")
@@index([seller_id], map: "FK_79a3ae0442388a2418ec67a3120")
@@index([company_id], map: "FK_a0503db1630a5b8a4d7deabd556")
@@index([bundle_id], map: "FK_f35662270f8bcb3f26dfd6e9fda")
조정후의 모델입니다.
model Product {
id Int @id @default(autoincrement())
createdAt DateTime @default(now()) @db.DateTime(6) @map("created_at")
updatedAt DateTime @default(now()) @db.DateTime(6) @map("updated_at")
deletedAt DateTime? @db.DateTime(6) @map("deleted_at")
sellerId Int @map("seller_id")
bundleId Int? @map("bundle_id")
categoryId Int @map("category_id")
companyId Int @map("company_id")
isSale Int @db.TinyInt @map("is_sale")
name String @db.VarChar(128)
description String? @db.VarChar(255)
deliveryType String @db.VarChar(128) @map("delivery_type")
deliveryFreeOver Int? @map("delivery_free_over")
deliveryCharge Int @map("delivery_charge")
img String @db.VarChar(255)
carts Cart[]
orderProducts OrderProduct[]
category Category @relation(fields: [categoryId], references: [id], onDelete: NoAction, onUpdate: NoAction)
seller Seller @relation(fields: [sellerId], references: [id], onDelete: NoAction, onUpdate: NoAction)
company Company @relation(fields: [companyId], references: [id], onDelete: NoAction, onUpdate: NoAction)
productBundle ProductBundle? @relation(fields: [bundleId], references: [id], onDelete: NoAction, onUpdate: NoAction)
productOptions ProductOption[]
productRequiredOptions ProductRequiredOption[]
@@index([categoryId])
@@index([sellerId])
@@index([companyId])
@@index([bundleId])
@@map("product")
}
이제 앞으로 모델이나 스키마를 수정한 후에는 push
명령어를 사용할 수 있습니다.
npx prisma db push
아래 패키지를 설치합니다.
npm install @prisma/client
API를 이용하기 위해 prisma.service.ts
를 다음과 같이 추가합니다.
import { Injectable, OnModuleInit } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}
다른 모듈에서도 사용 가능 하도록 모듈로 만들어 보겠습니다.
다음과 같이 서비스를 내보낼수 있도록prisma.module.ts
을 작성합니다.
@Module({
providers: [PrismaService],
exports: [PrismaService],
})
export class PrismaModule {}
그리고 사용하고자 하는 모듈에 import 합니다.
저는 기존의 TypeORM이있던 Auth모듈에 추가를 했습니다. (관련커밋)
이제 프리즈마 API를 사용해 기존의 서비스로직을 고쳐나가면 됩니다.
@Injectable()
export class AuthService {
constructor(private readonly prisma: PrismaService,) {}
...
}
잠깐 사용해보았는데, 컬럼 타입 추론 가능해지니 정말 편하네요...✌️
메소드들은 더 사용해봐야 익숙해지겠지만, TypeORM 0.3과 문법자체가 그리 차이나진 않는 것 같습니다.
Prisma
꾸준히 인기를 얻어 작년 하반기 TypeORM의 다운로드 수를 넘긴, Prisma(프리즈마)를 도입해보려 합니다.
TypeORM의 문제
타입세이프 하지 않다!
TypeORM은 Entity를 만들어 DB의 각 테이블을 객체로 추상화해 사용합니다.
그러나 아래와 같이 타입추론이 제대로 되지 않는 경우가 발생합니다.
모두 타입세이프 하지 않아서 발생하는 문제입니다.
실제로는 존재하지 않는 데이터인데 컴파일 단계에서 확인할 수 없으니, 타입스크립트를 사용하는 의미가 전혀 없는 상황이 됩니다. 🤔
Prisma는 다릅니다
Prisma는타입세이프한 ORM입니다. 위와 같은 문제점이 발생하지 않습니다.
다른 차이점은 TypeORM보다 추상화가 되어있어, SQL과 거리가 좀 더 먼 문법을 사용한다는 점입니다.
저는 Nest를 배우기 전까지는 쌩쿼리(?)를 사용했었기에 TypeORM도 충분히 개발친화적이라고 느꼈었는데요. Prisma는 그것보다 더 추상화가 잘 되어있다니 기대가 됩니다. ✌️