NextJS 와 NestJS를 같이 써보자 (2) - prisma

pudding·2023년 3월 15일
2
post-thumbnail

지난 포스트 에 이어 prisma를 적용해보려고 한다.
다른 ORM 과의 비교 문서인데 궁금하신 분들은 한번 읽어보세요. 여기

🎉 1. docker compose 작성

우선 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

🎉 2. prisma 설치 & initialize

라이브러리 설치를 하자.

npm install prisma --save-dev
npx prisma init --datasource-provider mysql

🎉 3. env 설정, prisma schema 작성

2번의 과정을 거치면 .env 파일과 prisma/schema.prisma 파일이 생긴 것을 볼 수 있다.

✨ 3-1 .env 파일 수정

env 파일을 1번 과정의 설정대로 아래와 같이 수정한다. user로 접근하려면 추가 권한 설정이 필요하니 우선 root 로 설정을 하자.

DATABASE_URL="mysql://root:rootpw@localhost:3306/my_db"

✨ 3-2 prisma/schema.prisma 파일 수정

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
}

🎉 4. prisma migrate

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을 할때마다 이 테이블에 모두 기록될 것이다.

* 실서버 DB로 migration은 어떻게 할까?

먼저 .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 이 잘 되었는지 확인해보자.

🎉 5. prisma service 생성

이제 데이터를 가져올 서비스를 만들자. 귀찮으니 @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();
  }
}

🎉 6. user / module, controller, service 생성

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 값을 넘기면 유저 정보를 넘겨주는 정도의 기능만 코딩해보자.

✨ 6-1. 의존성 주입을 위해 user.module.ts에 PrismaService 추가

@Module({
  controllers: [UserController],
  providers: [UserService, PrismaService],
})
export class UserModule {}

✨ 6-2. user.service.ts에 function 등록

@Injectable()
export class UserService {
  constructor(private readonly db: PrismaService) {}

  async findUserById(id: number) {
    return await this.db.user.findUnique({
      where: {
        id,
      },
    });
  }
}

✨ 6-3. user.controller.ts에 api 등록

@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);
  }
}

7. 🎉 데이터 입력

테스트를 위한 데이터가 필요하다. 아래의 명령어를 입력하자.

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로 데이터를 추가해보자.

9. 🎉 class-transformer, class-validator

그런데 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,
        })
      );
      ...
    }

    ...
  }

  ...
}

결과


원하는 값이 잘 나오는 것을 확인할 수 있다.

🎊 PrismaClient 는 어떻게 user, post 를 알고 있을까?

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 명령도 사용해보도록 하겠다.

📖 참고 문서

📁 github 주소

https://github.com/wlstn4115/next-with-nest

profile
I don't wanna be one of them.

0개의 댓글