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를 실행하면 만들어진다.
- schema를 바탕으로 자동 생성되는 쿼리 라이브러리.
- 3️⃣ Prisma Migrate
- schema 변경사항을 실제 DB에 반영해주는 도구.
npx prisma migrate dev를 실행하면 마이그레이션 파일이 만들어지고 DB에 적용된다.
- schema 변경사항을 실제 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 구조를 동기화할 수 있다.