지난 포스트 에 이어 prisma를 적용해보려고 한다.
다른 ORM 과의 비교 문서인데 궁금하신 분들은 한번 읽어보세요. 여기
우선 docker 부터 설치하고 docker-compose.yml 파일 작성을 해보자.
# docker-compose.yml
version: "3.8"
volumes:
next-with-nest:
services:
mariadb:
image: mariadb:10.5.19
command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_unicode_ci
restart: always
environment:
MYSQL_ROOT_PASSWORD: rootpw
MYSQL_DATABASE: my_db
MYSQL_USER: user
MYSQL_PASSWORD: userpw
volumes:
- next-with-nest:/var/lib/mysql
ports:
- 3306:3306
다 작성하고 컨테이너를 올려보자. 작성한 파일이 있는 위치에서 아래의 명령어를 입력한다.
docker-compose up -d
컨테이너가 올라가면 local DB 는 준비 완료다. 확인해보자.
docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
c0f0decf2df8 mariadb:10.5.19 "docker-entrypoint.s…" 12 minutes ago Up 12 minutes 0.0.0.0:3306->3306/tcp next-with-nest-mariadb-1
라이브러리 설치를 하자.
npm install prisma --save-dev
npx prisma init --datasource-provider mysql
2번의 과정을 거치면 .env 파일과 prisma/schema.prisma 파일이 생긴 것을 볼 수 있다.
env 파일을 1번 과정의 설정대로 아래와 같이 수정한다. user로 접근하려면 추가 권한 설정이 필요하니 우선 root 로 설정을 하자.
DATABASE_URL="mysql://root:rootpw@localhost:3306/my_db"
model 정보를 간단히 추가해주자. 해당 모델 정보로 연결된 DB에 table이 생성될 예정이다.
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
npx prisma migrate dev --name init
위의 커맨드를 입력하면 @prisma/client 패키지가 생성되고 prisma 디렉토리에 migration 정보 파일이 생성된다. DB에도 migration 정보 테이블과 함께 모델로 명시해주었던 테이블이 생성된 것을 확인할 수 있다.
그런데 테이블명이 맘에 안든다. 나는 테이블 앞의 문자를 소문자로 변경하고 싶다. schema.prisma 파일의 model 명을 User->user, Post->post 로 변경해주고 model 안에 서로 참조하고 있는 Post와 User 도 변경해준다. 그리고 다시 아래의 커맨드를 입력한다.
npx prisma migrate dev --name change_table_name
파일이 migration 정보 파일이 추가로 생성되고 테이블명이 변경된다. 그리고 추가로 눈여겨볼 것은 _prisma_migrations
테이블이다.
migration history 데이터가 들어있다. migrate나 rollback을 할때마다 이 테이블에 모두 기록될 것이다.
먼저 .env 파일에 작성했던 것 처럼 .env.production 파일 생성 후 실서버 DB 정보를 입력하자.
DATABASE_URL="mysql://<user>:<password>@<host>:<port>/<database>"
그리고 dotenv-cli 가 필요하다.
npm install dotenv-cli --save-dev
npx dotenv -e .env.production -- npx prisma migrate deploy
실서버에 migration 이 잘 되었는지 확인해보자.
이제 데이터를 가져올 서비스를 만들자. 귀찮으니 @nestjs/cli 사용합니다. 생성되는 모든 spec 파일은 지우자.
npm install @nestjs/cli --save-dev
cd server
npx nest g s
? What name would you like to use for the module? prisma
PrismaService에는 다음과 같이 입력한다.
import { Injectable, OnModuleDestroy, OnModuleInit } from "@nestjs/common";
import { PrismaClient } from "@prisma/client";
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
async onModuleInit() {
await this.$connect();
}
async onModuleDestroy() {
await this.$disconnect();
}
}
npx nest g mo # module 생성
? What name would you like to use for the module? user
npx nest g co # controller 생성
? What name would you like to use for the controller? user
npx nest g s # service 생성
? What name would you like to use for the service? user
간단하게 id 값을 넘기면 유저 정보를 넘겨주는 정도의 기능만 코딩해보자.
@Module({
controllers: [UserController],
providers: [UserService, PrismaService],
})
export class UserModule {}
@Injectable()
export class UserService {
constructor(private readonly db: PrismaService) {}
async findUserById(id: number) {
return await this.db.user.findUnique({
where: {
id,
},
});
}
}
@Controller("user")
export class UserController {
constructor(private readonly userService: UserService) {}
@Get("/:id")
async findUserById(@Param("id") id: number): Promise<user | null> {
return await this.userService.findUserById(id);
}
}
테스트를 위한 데이터가 필요하다. 아래의 명령어를 입력하자.
npx prisma studio
Environment variables loaded from .env
Prisma schema loaded from prisma\schema.prisma
Prisma Studio is up on http://localhost:5555
5555포트로 뭔가가 올라갔다. 들어가보면 prisma 에서 제공하는 studio 가 올라가있다.
user 모델에 Add record로 데이터를 추가해보자.
그런데 NestJS 개발자라면 당연히 알겠지만 API 호출하면 에러가 날 것이다.
서버에러를 보면,
Argument id: Got invalid value '1' on prisma.findUniqueUser. Provided String, expected Int.
이라고 하는데 url 로 들어오는 모든 파라미터는 string 으로 들어온다. 하지만 아까 user 모델의 id 는 Int라서 타입 불일치로 오류가 난다. 그래서 추가 설정이 필요하다. 다음 패키지를 설치하자.
npm install class-transformer class-validator
그리고 server/main.ts 파일에 약간의 설정도 추가해준다.
export module Main {
let app: INestApplication;
export async function getApp() {
if (!app) {
...
app.useGlobalPipes(
new ValidationPipe({
transform: true,
})
);
...
}
...
}
...
}
원하는 값이 잘 나오는 것을 확인할 수 있다.
6-2번의 this.db.user.~~ 이 부분. PrismaService 는 PrismaClient 를 확장하여 만든 서비스다. 그렇다면 PrismaClient는 user 를 어떻게 알고 불러오는 걸까? 내가 만약 schema.prisma 파일에 product 모델을 추가하면 PrismaClient는 product를 알고 .product로 접근할 수 있을 것이다. PrismaClient는 @prisma/client에 있는 놈인데 아까 npx prisma migrate dev 커맨드를 입력할 때 자동으로 해당 패키지가 설치가 됐던 것을 기억할 것이다. migrate 할 때 내부적으로 migration 이외에도 model명으로 아티팩트를 추가하는 prisma generate 라는 명령도 하는데 이 때 node_module/.prisma/client 이 때 아래 파일들에 등록된다. 궁금하면 한번 들여다보면 된다. 나중에 배포할 때 prisma generate 명령도 사용해보도록 하겠다.