스웨거 반복 작업 탈출 + 모델 속성의 중앙화: Prisma Class Generator

인한별
인한별
조회 수10

NestJS 프로젝트를 진행하다 보면, 데이터베이스 스키마와 API 명세서 사이의 간극을 메우기 위해 끊임없는 단순 반복 작업에 직면하게 됩니다.

새로운 테이블이나 컬럼이 하나 추가될 때마다 Prisma 스키마를 수정하고, 요청/응답 DTO 파일을 새로 열어 @ApiProperty를 달아주고, class-validatorclass-transformer 데코레이터를 일일이 붙여야 하죠.


하지만 단순히 귀찮은 반복 작업(보일러플레이트)만을 피하고자 만든 것은 아닙니다. 가장 근본적이고 중요한 목적은 바로 '특정 모델에 대한 속성들의 완벽한 중앙화'였습니다.


이 두 가지 치명적인 문제(스웨거 작성 피로도 + 속성의 파편화)를 한 번에 해결하기 위해 AI에게 지시하여 AI에게 업무를 맡기기에는 일관된 모델 관리가 어려우며, 이 문제점을 해결하기위해 높은 비용의 토큰을 소비하기에는 비효율적이라 판단하였습니다. 직접 prisma-class-generator 라이브러리를 개발하게 되었습니다.


개발사유

NestJS 생태계에서는 일반적으로 다음과 같은 흐름으로 엔티티와 DTO를 분리해서 관리합니다:

  1. DB 스키마 정의: schema.prisma
  2. 비즈니스 로직: 데이터 계층 응답을 위한 순수 데이터 모델 User)
  3. 요청 검증: API 요청/응답 파라미터 검증을 위한 Request/Response DTO class-validator 사용)
  4. 문서화: 클라이언트를 위한 Swagger 문서용 DTO 속성 정의 @ApiProperty 사용)


개발 관점에서는 역할의 분리가 잘 되어있는 것 같지만, 유지보수 측면에서 보면 "가장 핵심이 되는 하나의 데이터 속성이 여러 파일에 흩어지게 된다"는 이슈가 있습니다.


예를 들어, User 테이블에 회원의 상태를 나타내는 isActive (Boolean) 컬럼을 하나 추가하거나, 타입을 변경했다고 가정해 보겠습니다

개발자는 Prisma 스키마를 수정하고, Model 인터페이스를 수정하고, Request DTO 파일을 열어 @IsBoolean()을 붙이고, Response DTO를 수정하고, 추가로 모든 곳에 @ApiProperty() 명세를 또 적어줘야 합니다. 단 한 곳이라도 빼먹으면 런타임 검증 에러가 나거나 스웨거 문서가 실제 스펙과 달라지는 이슈가 발생할 수 있습니다.


저는 이 파편화의 늪에서 벗어나, 프리즈마 모델을 모델의 기준으로 모델 속성들을 중앙에서 관리할 수 있는 도구가 있으면 좋겠다고 판단했습니다.


prisma-class-generator 란?

prisma-class-generator는 Prisma Schema(schema.prisma)라는 중앙화된 단일 문서 하나만 정의하면, 파싱을 통해 NestJS 환경에 최적화된 TypeScript 클래스(Model 및 DTO)를 자동으로 뽑아주는 Prisma Generator 라이브러리입니다.

@nestjs/swagger@ApiProperty는 물론, class-validatorclass-transformer의 검증 데코레이터까지 완벽하게 결합되어 생성됩니다. 따라서 개발자는 오직 schema.prisma 하나만 관리하면 모든 문서화와 검증 규칙이 알아서 동기화됩니다.


핵심 특징

  • 완벽한 모델 속성의 중앙화: 데이터베이스 스키마와 DTO 속성, Swagger 문서화 내용이 오직 schema.prisma 한 곳에서 모두 통제됩니다.
  • 완벽한 NestJS 결합: 추가 작업 없이 바로 Swagger 명세에 포함시킬 수 있고, NestJS의 파이프(validation pipe) 구조를 통한 검증이 즉시 가능합니다.
  • Model과 DTO의 분리 제어: 순수 타입과 Swagger 속성만 포함된 Model 클래스와, 검증 데코레이터까지 전부 포함된 DTO 클래스를 독립적으로 생성하고 관리할 수 있습니다.
  • 강력한 커스텀 타입 대응: 실무에서 빈번히 사용되는 Boolean, BigInt, Decimal 등에 대해, 어떤 커스텀 데코레이터나 파이프 경로를 쓸 것인지 schema.prisma에서 직접 설정할 수 있게 설계했습니다. 프로젝트별 내부 컨벤션에 완전히 융화됩니다.
  • 유연한 네이밍 컨벤션: 조직마다 다른 구조에 맞출 수 있도록 클래스의 접두사/접미사(Prefix/Suffix) 설정이 가능합니다.



사용 방법

1. 패키지 설치

개발 의존성으로 패키지를 설치해 줍니다.

npm install -D @inhanbyeol/prisma-class-generator

(참고: 생성된 코드는 @nestjs/swagger, class-validator, class-transformer를 임포트하므로 해당 패키지들이 프로젝트에 설치되어 있어야 합니다.)

2. schema.prisma에 속성 중앙화 설정하기

이곳이 이제 모든 DTO 모델의 관제센터(Single Source of Truth)가 됩니다. 목적에 따라 제너레이터를 등록해 줍니다.

// 1. 순수 모델(Model) 생성기 
generator model {
  provider = "@inhanbyeol/prisma-class-generator"
  type     = "model"
  output   = "./generated-model"
}

// 2. 검증이 포함된 DTO 생성기
generator dto {
  provider = "@inhanbyeol/prisma-class-generator"
  type     = "dto"
  output   = "./generated-dto"
  
  // 회사/프로젝트 네이밍 컨벤션에 맞게 Prefix, Suffix 셋팅 가능!
  filePrefix  = "base-"
  classPrefix = "Req"
  classSuffix = "Dto"
  
  // (필수) 커스텀 데코레이터가 필요한 특수 타입 처리 지정 (유연성의 핵심)
  isBooleanName        = "MyBool"
  isBooleanPath        = "@/custom/bool"
  // ... (BigInt, Decimal 타입에 대해서도 동일한 방식으로 지정 가능)
}

// 모델 작성 시 /// 주석을 달면 Swagger Description과 DTO 주석으로 한 번에 들어갑니다!
model User {
  id       Int     @id @default(autoincrement()) /// 유저 고유 ID
  email    String  @unique /// 이메일 주소
  name     String? /// 이름
  isActive Boolean @default(true) /// 활성 상태 여부
}

3. Generate 실행

npx prisma generate

명령어 한 줄이면 지정된 output 디렉토리에 스웨거 스펙과 검증 로직이 동기화된 모든 클래스가 즉각 쏟아져 나옵니다.

결과물 미리보기

속성을 한 곳에서 관리(schema.prisma) 했을 때 어떤 코드가 자동으로 떨어지는지 확인해 보세요.

// generated-dto/base-user.dto.ts
import { ApiProperty } from '@nestjs/swagger';
import { Prisma } from '@prisma/client';
import { IsInt, IsNotEmpty, IsString } from 'class-validator';
import { MyBool } from '@/custom/bool';
import { Type } from 'class-transformer';

export class ReqUserDto {
  /** 유저 고유 ID */
  @ApiProperty({ type: Number })
  @Type(() => Number)
  @IsInt()
  id!: number;

  /** 이메일 주소 */
  @ApiProperty({ type: String })
  @Type(() => String)
  @IsNotEmpty()
  @IsString()
  email!: string;

  /** 이름 */
  @ApiProperty({ type: String, nullable: true })
  @Type(() => String)
  @IsString()
  name!: string;

  /** 활성 상태 여부 */
  @ApiProperty({ type: Boolean })
  @MyBool() // <-- 스키마 설정해둔 커스텀 데코레이터 로직이 완벽히 매핑됩니다!
  isActive!: boolean;
}

이제 더 이상 컬럼 하나를 수정하기 위해 3~4개의 파일을 찾아다닐 필요가 없습니다. 모든 것은 Prisma Schema 단 한 곳에서 이루어집니다.


마치며

prisma-class-generator의 핵심은 단순한 타이핑을 줄여주는 것이 아니라, 강력한 속성 중앙화 관리를 통해 데이터 스펙 불일치(Out-of-sync)라는 시스템적 버그 요소를 원천 차단하는 데 있습니다.

매번 반복되는 Swagger 명세 관리와 보일러플레이트 작성의 무거운 짐을 이 라이브러리에게 넘겨주세요. 남는 시간과 에너지는 백엔드 애플리케이션의 핵심 비즈니스 로직과 아키텍처를 설계하는 데 투자하시길 바랍니다!


궁금한 점이나 버그 제보, 발전 방향에 대한 기여(PR)는 언제든지 환영합니다.

댓글 0

댓글은 회원만 작성할 수 있습니다.

로그인하고 댓글 달기
댓글을 불러오는 중입니다...