Prisma, 넌 누구니?

MyBatis의 노가다와 TypeORM의 배신을 넘어, Prisma는 얼마나 합리적인가

Jun Noh

방황의 역사: 왜 우리는 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)TypeORMPrisma
개발 철학쿼리 제어권이 최우선객체 지향적 매핑타입 안정성(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도 완벽하진 않다 (단점)

비판적으로 보자면, 분명 단점도 있다.

  1. 무겁다: Rust로 작성된 쿼리 엔진 바이너리를 포함하기 때문에 node_modules 용량이 꽤 크다. AWS Lambda 같은 서버리스 환경에서는 콜드 스타트(Cold Start) 문제가 발생할 수 있다고 한다(몰러 안써봐서…ㅋㅋㅋ). (최근엔 많이 개선되었다고 한다.)
  2. Raw Query의 유혹: 아주 복잡한 통계 쿼리나 DB 고유의 기능을 써야 할 때는 Prisma의 API로 해결이 안 된다. 결국 $queryRaw를 써야 하는데, 이러면 Prisma의 장점인 타입 안정성을 일부 포기해야 한다.
  3. 마법의 대가: 내부적으로 어떻게 쿼리가 나가는지 숨겨져 있다 보니, 무심코 쓴 include가 엄청나게 비효율적인 조인 쿼리를 날릴 수도 있다. (로그를 확인하는 습관이 필요하다.)

결론: 왜 1인 개발자에게 Prisma인가?

과거의 나는 ‘성능’과 ‘제어권’에 집착해서 Old School 방식을 고수했었다. 하지만 혼자 서비스를 만들다 보니 깨달았다.

서버가 0.01초 느린 건 돈으로 해결되지만, 내가 버그를 잡느라 밤을 새우는 건 돈으로도 해결이 안 된다.

Prisma는 내가 실수할 확률을 시스템적으로 차단해 준다.

데이터베이스 스키마 변경이 두려워서 기획을 수정하던 쫄보 시절은 이제 안녕이다.

Prisma를 쓰면 스키마 변경은 그냥 ‘타이핑 몇 번’일뿐이니까.

아직 TypeORM이나 MyBatis를 붙들고 있다면, 딱 한 번만 찍먹 해보길 권한다.

prisma generate가 주는 쾌감을 맛보면, 다시는 XML 파일로 돌아가고 싶지 않을 것이다.

마침.

다른 글 보기