Skip to content
Go back

Prisma JSON 필드에 타입 붙이기

Published:  at  10:00 AM

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

한계점

내가 느낀 장점

솔직히 이거 쓰고 나서 개발이 훨씬 편해졌다:

  1. 타입 단언 안 해도 됨 - as UserProfile 같은 거 필요 없음
  2. 자동완성 제대로 됨 - IDE가 Json 필드 구조를 정확히 앎
  3. 컴파일 타임에 에러 잡힘 - 런타임 전에 문제 발견
  4. String enum 대체 가능 - DB enum 안 써도 타입 안전하게

대안들

prisma-json-types-generator가 제일 심플하고 좋다.

결론

Prisma Json 필드 때문에 고생하고 있다면 바로 도입하길. 설정도 간단하고, 기존 코드 안 건드려도 되고, 타입 안정성은 확실히 올라간다.

특히 이런 경우라면 필수:

한 번 써보면 이거 없이 어떻게 Prisma 썼나 싶을 거다.


Share this post on:

Previous Post
NestJS DDD‧CQRS로 모듈 간 의존성 최소화하기 - 급여 시스템 아키텍처 가이드
Next Post
Claude Code 설정과 최적화 - 개발자를 위한 완벽 가이드