[ORM] Prisma 개념, ERD -> Prisma schema로 변환해보자.

 

Prisma 란

Prisma는 Node.js / TypeScript 환경에서 DB를 다루기 위한 ORM(Object Relational Mapper) 이다.

쉽게 말하: SQL을 직접 짜는 대신, JavaScript/TypeScript 코드로 DB를 다룰 수 있게 해주는 도구다.

// SQL 없이 이렇게 쓸 수 있다
const users = await prisma.user.findMany();

 

 

그러면 여기서 ORM이 뭔지 알아보자.

DB는 SQL이라는 언어로 소통한다. 그런데 백엔드 코드는 JavaScript로 쓰고 있으니, 이 둘이 직접 대화하기가 어렵다.

ORM은 이 둘 사이의 통역사 역할을 한다.

내 코드 (JS/TS)  →  ORM (Prisma)  →  DB (PostgreSQL 등)

 

  SQL 직접 Prisma(ORM)
코드 스타일 SELECT * FROM users WHERE id = 1 prisma.user.findUnique({ where: { id: 1 } })
타입 안정성 ❌ 없음 ✅ TypeScript 자동완성 지원
실수 감지 런타임에 발견 컴파일 타임에 발견

 

 

Prisma의 구성 요소

Prisma는 크게 3가지로 이루어진다.

  • 1️⃣ Prisma Schema (schema.prisma)
    • DB 구조를 정의하는 파일. 테이블, 컬럼, 관계를 여기에 적는다.
  • 2️⃣ Prisma Client
    • schema를 바탕으로 자동 생성되는 쿼리 라이브러리.
      npx prisma generate를 실행하면 만들어진다.
  • 3️⃣ Prisma Migrate
    • schema 변경사항을 실제 DB에 반영해주는 도구.
      npx prisma migrate dev를 실행하면 마이그레이션 파일이 만들어지고 DB에 적용된다.
schema.prisma  →  (generate)  →  Prisma Client  →  DB 쿼리
             ↘  (migrate)   →  실제 DB에 테이블 생성

 


schema.prisma 파일 뜯어보기

schema.prisma는 3개의 블록으로 구성된다.

datasource : 어떤 DB를 쓸지!

datasource db {
  provider = "postgresql"  // mysql, sqlite 등도 가능
  url      = env("DATABASE_URL")  // .env 파일에서 DB 주소를 읽어옴
}

generator : 뭘 생성할지!

generator client {
  provider = "prisma-client-js"  // Prisma Client를 JS로 생성
}

model : 테이블 정의!

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  nickname  String?  // ?는 null 허용 (optional)
  createdAt DateTime @default(now())
}

각 줄은 필드명 / 타입  / [어트리뷰트들] 구조로 이루어진다.

 

Prisma 타입

String 문자열 (varchar, text 모두)
Int 정수
Boolean true / false
DateTime 날짜+시각
Json JSON 객체
String[] 배열 (PostgreSQL만 지원)

 

어트리뷰트 

@id Primary Key
@default(autoincrement()) 자동 증가 ID
@default(now()) 현재 시각 기본값
@unique 유니크 제약
@map("컬럼명") 실제 DB 컬럼명 지정
@@map("테이블명") 실제 DB 테이블명 지정
@updatedAt 업데이트 시 자동으로 현재 시각 저장

 


5-1. 기본 테이블 변환

DBML

Table users {
  id         integer [primary key, increment]
  email      varchar [unique, not null]
  password   varchar
  nickname   varchar
  created_at timestamp
}

Prisma

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  password  String?           // not null 없으면 ? 붙여서 optional
  nickname  String?
  createdAt DateTime @default(now()) @map("created_at")

  @@map("users")
}

포인트:

  • DBML의 [not null]이 없는 필드 → Prisma에서 ? (nullable)
  • snake_case 컬럼명 → camelCase 필드명 + @map()으로 실제 DB 컬럼명 유지
  • 테이블명도 @@map()으로 소문자 복수형 유지

 

ERD -> Prisma scheme 변환하기 

이미 dbdiogram으로 작성한 ERD가 있어서, 이걸 Prisma로 옮겨 보면서 공부해보았다. 

// 1. Auth & User Management (김가영 담당)
// 유저 기본 정보 및 활동 점수 관리
Table users {
  id integer [primary key, increment]
  email varchar [unique, not null]
  password varchar [note: '소셜 로그인 유저는 비어있을 수 있음']
	name varchar
  profile_img text
  provider varchar [default: 'local', note: 'local, github']
  provider_id varchar [note: '깃허브 고유 ID']
  created_at timestamp
}

// 점수 변동 이력 (잔디 그래프 및 히스토리용)
Table score_logs {
  id integer [primary key, increment]
  team_member_id integer [ref: > team_members.id]
  amount integer // +50, +10 등
  reason varchar // ADOPTED_COMMENT, DAILY_CHECKIN 등
  created_at timestamp
}

// 2. Team Workspace (정애리 담당)
// 팀 생성 및 멤버십/권한 관리
Table teams {
  id integer [primary key, increment]
  name varchar [not null]
  description text
  slack_webhook_url text [note: '슬랙 알림 연동용']
  created_at timestamp
}

Table team_members {
  id integer [primary key]
  team_id integer [ref: > teams.id]
  user_id integer [ref: > users.id]
  role varchar // LEADER, MEMBER
  status varchar [default: 'PENDING', note: 'INVITED, JOINED, BANNED'] // 상태 추가
  score integer [default: 0]
  joined_at timestamp
}

// 3. Troubleshooting & Error Logs (김병성, 한재민 담당)
// 에러 포스팅 및 기술적인 로그 데이터 저장 -> 알림갈 사람 지정하게 해야할까요
Table error_issues {
  id integer [primary key, increment]
  user_id integer [ref: > users.id]
  team_id integer [ref: > teams.id]
  title varchar [not null]
  content text [note: '사용자가 작성한 상황 설명 (마크다운)']
  tag varchar[] [note: 'Frontend, Backend, DB 등']
  is_public boolean [default: false, note: '팀 외 전체 공개 여부']
  status varchar [default: 'unsolved', note: 'solved']
  created_at timestamp
  updated_at timestamp
}

Table error_logs {
  id integer [primary key, increment]
  issue_id integer [ref: > error_issues.id] // 게시글 하나에 2개 로그 연결가능
  log_type varchar [note: 'SENT(내 로그), RECEIVED(상대방 로그)']
  source varchar [note: 'Client, Server, DB, ExternalAPI 등']
  message text [note: '에러 메시지 요약']
  stack_trace text [note: '전체 스택 트레이스']
  request_data json [note: '요청 페이로드']
  response_data json [note: '응답 데이터']
  captured_at timestamp [note: '실제 에러 발생 시각']
}


// 4. Comments (정영호 담당)
Table comments {
  id integer [primary key, increment]
  issue_id integer [ref: > error_issues.id] 
  user_id integer [ref: > users.id]       
  parent_id integer [ref: > comments.id, note: 'null이면 원본 댓글, 값이 있으면 대댓글']
  content text [not null]
  is_adopted boolean [default: false]     
  reward_score integer [note: '채택 시 부여된 점수 기록']
  created_at timestamp
}

// 5. System Notifications 
Table notifications {
	id integer [primary key, increment]
  user_id integer [ref: > users.id]
  resource_id integer [note: '이동할 issue_id 등']
  type varchar [note: 'comment, adopt, team_invite']
  content text
  is_read boolean [default: false]
  created_at timestamp
}

// 그룹화 (시각적 구분용)
TableGroup Auth_User {
  users
  score_logs
  notifications
}

TableGroup Team_Management {
  teams
  team_members
}

TableGroup Core_Feature {
  error_logs
  error_issues
  comments
}

 

1. 기본 테이블 변환

Table users {
  id         integer [primary key, increment]
  email      varchar [unique, not null]
  password   varchar
  nickname   varchar
  created_at timestamp
}

->

model User {
  id        Int      @id @default(autoincrement())
  email     String   @unique
  password  String?           // not null 없으면 ? 붙여서 optional
  nickname  String?
  createdAt DateTime @default(now()) @map("created_at")

  @@map("users")
}

 

  • DBML의 [not null]이 없는 필드 → Prisma에서 ? (nullable)
  • snake_case 컬럼명 → camelCase 필드명 + @map()으로 실제 DB 컬럼명 유지
  • 테이블명도 @@map()으로 소문자 복수형 유지

2. 관계(Relation) 변환

Prisma는 FK(외래 키)를 단순히 숫자 ID로 끝내지 않고, 관계 필드도 함께 선언해야 한다.

Table team_members {
  id      integer [primary key]
  team_id integer [ref: > teams.id]
  user_id integer [ref: > users.id]
  role    varchar
}

->

 

model TeamMember {
  id     Int    @id @default(autoincrement())
  teamId Int    @map("team_id")
  userId Int    @map("user_id")
  role   String

  // 관계 필드 — 실제 DB 컬럼은 아니지만 Prisma가 JOIN을 처리해줌
  team Team @relation(fields: [teamId], references: [id])
  user User @relation(fields: [userId], references: [id])

  @@map("team_members")
}

// 반대편 모델에도 역방향 관계를 선언해야 함
model Team {
  // ...
  teamMembers TeamMember[]  // Team 하나에 여러 TeamMember
}

model User {
  // ...
  teamMembers TeamMember[]
}

 

  • @relation(fields: [FK필드], references: [상대PK]) 
  • 반대편 모델에도 반드시 역방향 배열 필드 추가 (TeamMember[])
  • 관계 필드 자체는 실제 DB에 컬럼으로 생성되지 않음

 

3. 자기참조 관계 (ex 댓글 → 대댓글)

대댓글 구조처럼 같은 테이블끼리 관계를 맺을 때

Table comments {
  id        integer [primary key, increment]
  parent_id integer [ref: > comments.id]
  content   text    [not null]
}

->

 

model Comment {
  id       Int     @id @default(autoincrement())
  parentId Int?    @map("parent_id")  // null이면 원본 댓글
  content  String

  // 자기참조 — 관계 이름을 따옴표로 명시해야 함
  parent  Comment?  @relation("CommentReplies", fields: [parentId], references: [id])
  replies Comment[] @relation("CommentReplies")

  @@map("comments")
}

 

  • 자기참조 관계는 반드시 "관계이름" 을 명시해야 한다
  • parent는 optional(?), replies는 배열([])

배열 타입 (PostgreSQL)

Table error_issues {
  category varchar[]
}

->

 

model ErrorIssue {
  category String[]  // PostgreSQL에서만 사용 가능
}

 

 

마이그레이션

sschema를 수정했다고 DB가 자동으로 바뀌지 않는다.
마이그레이션은 schema 변경사항을 실제 DB에 반영하는 과정이다.

# 개발 환경에서 마이그레이션 실행
npx prisma migrate dev --name init

# 마이그레이션 후 Prisma Client 재생성
npx prisma generate

# DB 현재 상태를 GUI로 확인
npx prisma studio

마이그레이션을 실행하면 prisma/migrations/ 폴더에 SQL 파일이 자동으로 생성된다.
이 파일들은 Git에 커밋해서 팀원과 공유해야 DB 구조를 동기화할 수 있다.