Prisma, 넌 누구니?
MyBatis의 노가다와 TypeORM의 배신을 넘어, Prisma는 얼마나 합리적인가
방황의 역사: 왜 우리는 DB 앞에서 고통받는가
신입 때를 기억하면, Spring - Oracle + MyBatis로 짜여진 레거시 프로젝트를 유지보수할 때, 제일 무섭고 하기 싫었던 작업 중 하나가 DB 스키마를 뜯어 고치는 일이었다.
쿼리의 응답 데이터나, 인자가 하나만 들어가거나 빠져도, 작성해야 할 파일의 양은 기본 4개 파일 이상이었다.
(DTO, Mapper Interface, XML, Service…)
그 흐름을 타고 따라가다 보면, 어느 샌가 내가 뭘 수정하고 있었더라.. 싶은 때가 많았다.
쿼리 수정이야 그냥 하면 되는데, 그거 하나 때문에 바꿔야 할 파일들이 너무 많았다.
그야말로 ‘복붙’과 ‘오타 찾기’의 연속이었다.
혼자 사이드 프로젝트를 하면서 Node.js를 공부하고, Express.js와 TypeORM을 알게 되면서 너무 신세계 같았다.
객체 지향이라니, 엔티티 구조를 짜면 DB가 알아서 생기는 게 완전 내가 바라던 천국 같이 느껴졌다.
하지만 그것도 깊이가 깊어지니, 복잡한 조인이나 통계 쿼리를 짤 때의 효율, 그리고 애매하게 느슨한 타입 추론 때문에 런타임에 어이없이 터지는 경우가 종종 있었다.
any 타입이 남발되거나, 관계 설정에서 실수가 잦았다.
결국은 다시 Old School로 날 데려갔다.
“그래, 튜닝하려면 SQL을 직접 짜야지”라며 합리화했다.
그러다 이번에 혼자 이것저것 개발하면서 Prisma라는 걸 알게 됐다.
처음엔 “또 새로운 ORM이야?” 하고 넘기려 했지만, 문서를 읽다 보니 뭔가 달랐다.
Prisma: 번역기가 아니라 ‘통역사’다
기존 ORM들이 “내 코드를 SQL로 번역해 주는 기계”였다면, Prisma는 나와 데이터베이스 사이에 있는 유능한 통역사 같았다.
가장 큰 차이점은 접근 방식이다.
- TypeORM: Code-First (코드를 짜면 DB가 바뀜)
- MyBatis/Legacy: DB-First or SQL-First (DB나 쿼리에 맞춰 코드를 짬)
- Prisma: Schema-First
Prisma는 schema.prisma라는 단 하나의 파일에서 모든 데이터 모델을 정의한다.
그리고 이 파일을 기준으로 **나만을 위한, 타입이 완벽하게 정의된 클라이언트(Client)**를 ‘생성’해준다.
이 ‘생성’이라는 개념이 핵심이다.
내가 짠 스키마가 곧바로 TypeScript 타입 정의(d.ts)가 되어버린다.
찍먹: 문법과 사용법
백문이 불여일타. 학생으로 돌아가 가장 기초적인 사용법을 훑어보자.
1. 스키마 정의 (The Single Source of Truth)
prisma/schema.prisma 파일에 작성하는 문법은 직관 그 자체다.
SQL DDL보다 읽기 쉽고, 자바 Entity보다 간결하다.
// DB 연결 설정
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
// 클라이언트 생성기 설정
generator client {
provider = "prisma-client-js"
}
// 모델(테이블) 정의
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[] // 관계 정의가 매우 명시적이다
createdAt DateTime @default(now())
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId Int
}
2. 마법의 명령어
npx prisma generate
이 명령어를 치는 순간, node_modules/.prisma/client 경로에 내 스키마에 딱 맞는 메서드들이 생성된다.
이제 에디터가 내 DB 구조를 완벽하게 이해하게 된다.
3. 실제 사용 (Querying)
이제 코드를 짤 때 마법이 일어난다.
TypeORM을 쓸 때 느꼈던 “이게 맞나?” 하는 불안감이 없다.
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
async function main() {
// 생성: 인자에 들어갈 필드명까지 자동완성 된다.
const newUser = await prisma.user.create({
data: {
email: "jun@indiehacker.com",
name: "Jun",
posts: {
create: { title: "Prisma 찍먹기" } // Nested Write: 관계된 데이터도 한 번에 넣는다.
}
}
})
// 조회: 조인(Join)이라는 개념 대신 'include'를 쓴다.
const usersWithPosts = await prisma.user.findMany({
where: {
email: { contains: "indie" } // 직관적인 필터링
},
include: {
posts: true // JOIN 문법 없이 데이터를 계층적으로 가져온다.
}
})
// usersWithPosts[0].posts -> 여기서 타입 추론이 완벽하게 된다.
}
“오타를 내고 싶어도 낼 수가 없다.”
VS Code에서 prisma.user.을 치는 순간, 사용할 수 있는 메서드들이 쫙 뜬다.
findMany 안에 wh까지만 쳐도 where가 자동 완성된다.
비교 분석: Old School vs TypeORM vs Prisma
1인 개발자로서, 그리고 배우는 입장에서 세 가지를 냉정하게 비교해봤다.
| 특징 | Old School (MyBatis/Spring) | TypeORM | Prisma |
|---|---|---|---|
| 개발 철학 | 쿼리 제어권이 최우선 | 객체 지향적 매핑 | 타입 안정성(Type Safety) |
| 생산성 | 낮음 (파일이 많음) | 높음 (초기 설정 복잡) | 최상 (자동완성, 직관성) |
| 타입 안정성 | 없음 (직접 맞춰야 함) | 중간 (느슨한 추론) | 완벽 (Generated Types) |
| 학습 곡선 | SQL만 알면 됨 | Decorator 등 학습 필요 | 독자 문법 있으나 매우 쉬움 |
| 단점 | 지루한 반복 작업(Boilerplate) | 복잡한 쿼리에서 꼬임 | 무거운 바이너리, 조인 성능 |
1. Old School보다 나은 점
MyBatis 시절, 컬럼 하나 추가하면 XML, DTO, 리포지토리를 다 열어야 했다. Prisma는 schema.prisma 한 줄 고치고 prisma db push 하면 끝이다. 유지보수의 고통이 1/10로 줄어든다.
2. TypeORM보다 나은 점
TypeORM은 런타임에 터지는 경우가 있다. “설마 이게 Null이겠어?” 하고 짰다가 서버가 죽는다. Prisma는 결과값이 User | null인지 User인지 명확하게 알려준다. 컴파일 단계에서 에러를 잡아주니 심리적 안정감이 다르다.
물론, Prisma도 완벽하진 않다 (단점)
비판적으로 보자면, 분명 단점도 있다.
- 무겁다: Rust로 작성된 쿼리 엔진 바이너리를 포함하기 때문에
node_modules용량이 꽤 크다. AWS Lambda 같은 서버리스 환경에서는 콜드 스타트(Cold Start) 문제가 발생할 수 있다고 한다(몰러 안써봐서…ㅋㅋㅋ). (최근엔 많이 개선되었다고 한다.) - Raw Query의 유혹: 아주 복잡한 통계 쿼리나 DB 고유의 기능을 써야 할 때는 Prisma의 API로 해결이 안 된다. 결국
$queryRaw를 써야 하는데, 이러면 Prisma의 장점인 타입 안정성을 일부 포기해야 한다. - 마법의 대가: 내부적으로 어떻게 쿼리가 나가는지 숨겨져 있다 보니, 무심코 쓴
include가 엄청나게 비효율적인 조인 쿼리를 날릴 수도 있다. (로그를 확인하는 습관이 필요하다.)
결론: 왜 1인 개발자에게 Prisma인가?
과거의 나는 ‘성능’과 ‘제어권’에 집착해서 Old School 방식을 고수했었다. 하지만 혼자 서비스를 만들다 보니 깨달았다.
서버가 0.01초 느린 건 돈으로 해결되지만, 내가 버그를 잡느라 밤을 새우는 건 돈으로도 해결이 안 된다.
Prisma는 내가 실수할 확률을 시스템적으로 차단해 준다.
데이터베이스 스키마 변경이 두려워서 기획을 수정하던 쫄보 시절은 이제 안녕이다.
Prisma를 쓰면 스키마 변경은 그냥 ‘타이핑 몇 번’일뿐이니까.
아직 TypeORM이나 MyBatis를 붙들고 있다면, 딱 한 번만 찍먹 해보길 권한다.
prisma generate가 주는 쾌감을 맛보면, 다시는 XML 파일로 돌아가고 싶지 않을 것이다.
마침.