본문 바로가기
코딩이야기/Next.js 공부

[Next.js] Prisma 개념 및 사용법

by TaeHyeon0412 2024. 6. 11.
  • 프리즈마는 대중적인 타입스크립트 지원 ORM 중 하나입니다. 프리즈마 설치 전에 기본적인 DB 개발 환경은 갖춰두어야 합니다. 설치 명령은 아래와 같습니다.
npm i prisma

 

  • 사용법
    설치 후 프로젝트에 프리즈마 적용을 위해 아래 명령을 실행합니다. 그러면, 루트에 prisma 폴더와 함께 그 아래 schema.prisma 파일이 새로 생성됩니다. 덤으로 데이터베이스 스키마 정보 연동을 위한 환경변수 설정에 필요한 .env 파일도 알아서 생성해줍니다.
npx prisma init
✔ Your Prisma schema was created at prisma/schema.prisma
You can now open it in your favorite editor.

warn You already have a .gitignore file. Don't forget to add `.env` in it to not commit any private information.

Next steps:
1. Set the DATABASE_URL in the .env file to point to your existing database. If your database has no tables yet, read https://pris.ly/d/getting-started
2. Set the provider of the datasource block in schema.prisma to match your database: postgresql, mysql, sqlite, sqlserver, mongodb or cockroachdb.
3. Run prisma db pull to turn your database schema into a Prisma schema.
4. Run prisma generate to generate the Prisma Client. You can then start querying your database.

More information in our documentation:
https://pris.ly/d/getting-started

 

순서대로 하려면 먼저 .env 파일을 확인하고 데이터베이스 정보를 입력해줍니다. 데이터베이스 정보(DATABASE_URL)는 개발자가 선택한 데이터베이스 유형마다 다른 패턴을 가지므로 프리즈마 공식 사이트에서 정확히 확인하고 기재해야 합니다.

  • 초기 .env 파일을 보면, 데이터베이스별 프리즈마 연동법을 설명한 상세 페이지가 기재돼 있으니 확인
  • 데이터베이스 정보는 노출해서는 안 되는 개인정보이므로, .gitignore 파일에 .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" // 여기 부분 변경

 

schema.prisma 파일에서 선택한 데이터베이스를 제공자(provider)로 변경합니다.

//schema.prisma

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

datasource db {
    provider = "postgresql" // 변경
    url      = env("DATABASE_URL")
}

(로컬 db라면 sqlite를 입력)

그후 모델을 만들어줍니다.

ex)

model User {
            id         Int      @id @default(autoincrement()) //첫번째 사용자는 자동으로 1부터 시작
            username   String   @unique
            phone      String?  @unique
            email      String?  @unique
            password   String?
            kakao_id   String?  @unique
            avatar     String?
            created_at DateTime @default(now())             //새유저가 만들어질때 그 시점의 날짜
            updated_at DateTime @updatedAt               //수정될 때 마다 수정된 시간을 넣음
      }

 

데이터베이스가 연결이 돼 있는 상태라면 터미널에서 아래 명령을 입력합니다.

.env 파일에 환경변수로 입력된 DATABASE_URL과 schema.prisma 파일에 입력된 스키마 모델을 토대로 새로운 데이터베이스를 만들어주는 명령입니다.

이 명령은 스키마를 변경했을 때마다 재실행해줘야 합니다.

npx prisma migrate dev


(마이그래이션 설치 이유 db에서 만든 변경사항을 계속해서 추적해야 되기 때문입니다.)

그러면 아래와 같은 질문이 뜨는데, 깃 커밋 메시지와 같은 개념입니다. 아래와 같이 모델과 관련성 있는 이름을 짓고 엔터를 누릅니다. 띄어쓰기가 허용되지 않으므로, 필요한 경우 '_'를 넣어 작성합니다.

? Enter a name for the new migration: add_user

위 일련의 행위는 아래 명령으로 한 번에 처리할 수도 있습니다.

npx prisma migrate dev --name ["모델 변경 설명(제목)"]

 

이후 prisma 폴더 아래 migrations 폴더가 새로 생성되고 그 하위에 날짜_모델변경설명(제목)형식의 폴더와 CREATE 문이 입력된 migration.sql 파일이 추가된 걸 확인할 수 있습니다.

이 시점에서 데이터베이스가 새로 생성된 것도 확인 가능한데, 약간의 시간차가 발생할 수 있으니, 새로고침을 계속 눌러줍니다.

또, 이때 프리즈마에서 아래 위치에 방금 만든 스키마를 위한 JS 파일과 타입까지 새로 생성했다는 사실도 확인할 수 있습니다.

마이그래이션 설치가 완료되면 db가 만들어집니다. 이것도 .gitignore에 추가합니다  => *.db, *.db-journal

-node_modules/prisma/client,

-node_modules/@prisma/@client

이 코드들 또한 개발에 활용 가능하므로 다음과 같이 import 해서 쓰면 됩니다.

 

  • Prisma Client 설정방법

util 폴더에 db.ts를 만들어 줍니다.

db.ts

import { PrismaClient } from "@prisma/client";를 임폴트 해준 뒤
new PrismaClient();로 초기화 해줍니다.

db.ts

이제 프리즈마를 사용하여 db를 불러올때에는 

const items = await db.item.findMany({

이런식으로 간편하게 불러올 수 있습니다.

프리즈마에서 제공하는 무료 DB프로그램은 아래의 명령어로 실행 가능합니다.
스키마를 변경한 경우, 실행중인 프리즈마 스튜디오 종료 후 재실행을 하여야 새로운 스키마가 반영된 DB를 볼 수 있습니다.

npx prisma studio

 

 

prisma 사용 방법 예시)

  • 등록
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import db from "@/app/_libs/_server/db";
 
const message = await db.message.create({
    data: {
      payload,
      chatRoomId,
      userId: session.id!,
    },
    select: { id: true },
  });
 
  revalidateTag("get-messages");
  return message;
}
cs

 

  • 조회 (JOIN)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
import db from "@/app/_libs/_server/db";
import getSession from "@/app/_libs/_server/session"//session은 쿠키 세션을 lib으로 만든것
 
async function getRoom(id: string) {
  const room = await db.chatRoom.findUnique({
    where: {
      id,
    },
    include: {
      users: {
        select: {
          id: true,
        },
      },
    },
  });
 
  if (room) {
    const session = await getSession();
    const canSee = Boolean(room.users.find((user) => user.id === session.id!));
    if (!canSee) {
      return null;
    }
  }
  //room에 있는 user.id와 session.id가 같을 경우에만 채팅방을 볼 수 있도록 함
  //다른 사용자가 url을 알아내서 강제로 보는것을 막기 위함
  return room;
}
//채팅방을 찾는 함수
cs

 

  • 조회 - 페이지네이션: skip, take 키 사용
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
"use server";
 
import db from "@/app/_libs/_server/db";
 
export async function getMoreItems(page: number) {
  const items = await db.item.findMany({
    select: {
      title: true,
      price: true,
      created_at: true,
      photo: true,
      id: true,
      userId: true,
 
      _count: {
        select: {
          hearts: true,
        },
      },
 
      hearts: {
        select: {
          userId: true,
        },
      },
    },
    skip: page * 8,
    take: 8,
    orderBy: {
      created_at: "desc",
    },
    //아이템 정렬
  });
  return items;
}
cs

 

  • 삭제
1
2
3
4
5
6
7
8
import client from "@/app/_libs/_server/client";
 
const deleted = await client.post.delete({
    where: {
      id: postId,
      userId,
    },
  });
cs

 

  • 로그 보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import { PrismaClient } from '@prisma/client';
 
 const db = new PrismaClient({
   log: [
     {
       emit: 'event',
       level: 'query',
     },
     {
       emit: 'stdout',
       level: 'error',
     },
     {
       emit: 'stdout',
       level: 'info',
     },
     {
       emit: 'stdout',
       level: 'warn',
     },
   ],
 });
 
 export default db;
cs

 

로그 형식을 지정하는 함수는 종단에서 실행될 수 없으므로, libs/hooks.ts 유틸로 분리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
export const setQueryLog = (roll: string, caller: string, result?: object | null=> {
   db.$on('query', (e) => {
     // SQL 키워드 자동 개행 및 색상 부여
     const query = e.query
       .toString()
       .replace(
         /(SELECT|UPDATE|DELETE|FROM|JOIN ON|WHERE|GROUP BY|HAVING|ORDER BY|LIMIT|OFFSET)\b/g,
         '\n\x1b[35m$1\x1b[0m',
       )
       .replace(/(DESC|ASC)\b/g, '\x1b[35m$1\x1b[0m')
       .replace(/,/g, '\n')
       .replaceAll('`''');
 
     console.log(chalk.black(chalk.bgCyan(` ❖ caller: ${caller} `)));
     console.log(chalk.black(chalk.bgCyan(` ❖ roll: ${roll} `)));
     console.log(`${chalk.cyan('Query: ')}${query}`);
     console.log(`${chalk.blue('Params: ')}${e.params}`);
     console.log(
       `${chalk.yellow('Duration: ')}${e.duration}ms ${e.duration >= 2 ? chalk.red('Too Lazy') : chalk.green('Good')}`,
     );
     result && console.log(`${chalk.cyan('Result:')}`);
     result && console.log(result);
     console.log(chalk.black(chalk.bgCyan(` ❖ DONE! ❖ `)));
   });
 };
cs

 

서버 콘솔 - 예시(상품목록 더 보기)

getPosts 
목록 조회

 Query:
 SELECT carrot_market_reloaded.User.id
  carrot_market_reloaded.User.username
  carrot_market_reloaded.User.email
  carrot_market_reloaded.User.password
  carrot_market_reloaded.User.phone
  carrot_market_reloaded.User.github_id
  carrot_market_reloaded.User.avatar
  carrot_market_reloaded.User.created_at
  carrot_market_reloaded.User.updated_at
 FROM carrot_market_reloaded.User
 WHERE (carrot_market_reloaded.User.id = ? AND 1=1)
 LIMIT ?
 OFFSET ?
 Params: [5,1,0]
 Duration: 0ms Good
 Result:
 [
   {
     id: 1,
     title: '잠만보 팔아요',
     description: '잠만보 팔아요 연락주세요!',
     views: 0,
     created_at: 2024-05-02T06:33:43.594Z,
     _count: { comments: 0, likes: 2 }
   }
 ]

 

 

++ 
vercel 배포후에는 npx prisma migrate dev를 하면 바로 적용되버리니
npx prisma migrate dev --create-only 를 사용하여 개발자모드로 확인 후에
npx prisma migrate deploy로 배포하면 됩니다.