Prisma 쓰다가 Json 필드 때문에 빡친 적 있나? 나는 많다. JsonValue
타입 때문에 매번 타입 단언하고, 런타임 검증 코드 짜고… 진짜 귀찮았다. 그러다 발견한 게 prisma-json-types-generator
다.
뭐가 문제였나
Prisma에서 Json 필드 쓰면 이런 일이 벌어진다:
// Prisma 스키마
model User {
id Int @id
profile Json // 이게 문제
}
// 실제 사용할 때
const user = await prisma.user.findFirst();
console.log(user.profile.theme); // 타입 에러! JsonValue에 theme 없음
profile
이 뭔지 Prisma는 모른다. 그냥 JsonValue
라고만 알고 있어서 매번 이렇게 해야 했다:
type UserProfile = {
theme: "dark" | "light";
notifications: boolean;
};
const profile = user.profile as UserProfile; // 타입 단언... 별로다
prisma-json-types-generator로 해결하기
이 패키지를 쓰면 Json 필드에 내가 원하는 타입을 바로 붙일 수 있다. 런타임 코드는 안 건드리고 타입만 바꿔준다. 성능 오버헤드도 없다.
설치부터 시작
npm install -D prisma-json-types-generator
Prisma 스키마 설정
// schema.prisma
generator client {
provider = "prisma-client-js"
}
generator json {
provider = "prisma-json-types-generator"
}
model User {
id Int @id @default(autoincrement())
email String @unique
/// [UserProfile] // 이 주석이 핵심
profile Json
/// !['draft' | 'published' | 'archived'] // String도 enum처럼 쓸 수 있음
status String @default("draft")
}
타입 선언하기
// src/types.ts
export {}; // 모듈로 만들어야 함
declare global {
namespace PrismaJson {
type UserProfile = {
theme: "dark" | "light";
notifications: boolean;
twitterHandle?: string;
};
}
}
이제 타입이 붙는다
npx prisma generate
실행하고 나면:
const user = await prisma.user.findFirst();
console.log(user.profile.theme); // 타입 에러 없음! 자동완성도 됨!
console.log(user.status); // 'draft' | 'published' | 'archived' 타입
두 가지 타입 연결 방법
1. 전역 네임스페이스 방식
복잡한 타입이나 여러 곳에서 쓸 타입은 이렇게:
/// [ProductMeta]
meta Json
/// [Tag]
tags Json[] // 배열도 됨
2. 인라인 방식
간단한 타입은 바로 스키마에:
/// !['physical' | 'digital']
type String
/// ![{ width: number; height: number }]
dimensions Json
실전 예시
model Product {
id Int @id @default(autoincrement())
/// [ProductMeta]
meta Json
/// [ProductVariant]
variants Json[]
/// !['active' | 'inactive' | 'discontinued']
status String @default("active")
/// ![{ min: number; max: number }]
priceRange Json
}
model Order {
id Int @id @default(autoincrement())
/// [ShippingAddress]
shipping Json
/// [OrderItem]
items Json[]
/// !['pending' | 'paid' | 'shipped' | 'delivered' | 'cancelled']
status String @default("pending")
}
타입 파일:
// types/prisma.ts
export {};
declare global {
namespace PrismaJson {
type ProductMeta = {
sku: string;
weight: number;
dimensions: {
width: number;
height: number;
depth: number;
};
};
type ProductVariant = {
id: string;
name: string;
price: number;
stock: number;
};
type ShippingAddress = {
name: string;
address: string;
city: string;
zipCode: string;
phone: string;
};
type OrderItem = {
productId: number;
variantId: string;
quantity: number;
price: number;
};
}
}
설정 옵션들
필요하면 이렇게 커스터마이징:
generator json {
provider = "prisma-json-types-generator"
allowAny = false // true면 미지정 Json이 any (기본: false -> unknown)
}
모노레포에서 주의할 점
모노레포에서 타입 선언 파일이 제대로 안 불러와지면 타입이 any
로 폴백된다. 이럴 때는:
// apps/api/src/index.ts (진입점)
import "@company/shared/types/prisma"; // 명시적으로 import
한계점
JsonFilter
같은 복잡한 필터 타입은 그대로 유지됨 (사용성 때문에)- Prisma v5+ 필요 (v4는 지원 중단)
내가 느낀 장점
솔직히 이거 쓰고 나서 개발이 훨씬 편해졌다:
- 타입 단언 안 해도 됨 -
as UserProfile
같은 거 필요 없음 - 자동완성 제대로 됨 - IDE가 Json 필드 구조를 정확히 앎
- 컴파일 타임에 에러 잡힘 - 런타임 전에 문제 발견
- String enum 대체 가능 - DB enum 안 써도 타입 안전하게
대안들
- ZenStack: 자체 DSL 써야 해서 좀 복잡함
- 수동 타입 단언: 매번
as Type
해야 해서 귀찮음 - 런타임 검증 (Zod 등): 성능 오버헤드 있음
난 prisma-json-types-generator
가 제일 심플하고 좋다.
결론
Prisma Json 필드 때문에 고생하고 있다면 바로 도입하길. 설정도 간단하고, 기존 코드 안 건드려도 되고, 타입 안정성은 확실히 올라간다.
특히 이런 경우라면 필수:
- Json 필드를 많이 쓰는 프로젝트
- 타입 안정성이 중요한 프로덕션 환경
- String enum 패턴을 자주 쓰는 경우
한 번 써보면 이거 없이 어떻게 Prisma 썼나 싶을 거다.