Nest.js를 배워보자

🏛️NestJS Prisma CRUD 모듈을 만들어보자!!

_Blue_Sky_ 2025. 12. 10. 00:37
728x90

 

 

-- prisma.wrk_danme_calc definition

CREATE TABLE `wrk_danme_calc` (
  `Open` varchar(6) NOT NULL COMMENT '영화open',
  `Code` varchar(2) NOT NULL COMMENT '영화code',
  `DanmesaCode` varchar(1) NOT NULL COMMENT '단매사code',
  `boxoffice_amount` bigint(20) unsigned DEFAULT NULL COMMENT '박스오피스 금액',
  `boxoffice_score` int(20) unsigned DEFAULT NULL COMMENT '박스오피스 관객수',
  `csb_amount` bigint(20) unsigned DEFAULT NULL COMMENT 'CSB 금액',
  `csb_score` int(20) unsigned DEFAULT NULL COMMENT 'CSB 관객수',
  `gonggb_amount` bigint(20) unsigned DEFAULT NULL COMMENT 'WB 순 박스오피스 CSB 공급가액',
  `suryoung_amount` bigint(20) unsigned DEFAULT NULL COMMENT '단매사 순 박스오피스 정산서 수령액(공급가액)',
  PRIMARY KEY (`Open`,`Code`,`DanmesaCode`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;



일때 nestjs prisma6 에서 crud하는 모듈,컨트롤러,서비스를 만들어줘


다음은 NestJS 모듈, 컨트롤러, 서비스를 사용하여 wrk_danme_calc 테이블에 대한 CRUD 작업을 수행하는 구현 방안입니다. 이 테이블은 복합 기본 키(Open, Code, DanmesaCode)를 가지고 있으므로, 단일 레코드를 조회, 수정, 삭제할 때 이 세 필드를 함께 처리해야 합니다.


1. Prisma Schema 설정 (필수)

NestJS 파일을 만들기 전에, schema.prisma 파일이 올바르게 설정되었는지 확인해야 합니다. 복합 키는 명시적으로 정의되어야 합니다.

// prisma/schema.prisma

model wrk_danme_calc {
  Open              String   @db.VarChar(6)    // 영화open
  Code              String   @db.VarChar(2)    // 영화code
  DanmesaCode       String   @db.VarChar(1)    // 단매사code
  boxoffice_amount  BigInt?  @db.UnsignedBigInt // 박스오피스 금액
  boxoffice_score   Int?     @db.UnsignedInt   // 박스오피스 관객수
  csb_amount        BigInt?  @db.UnsignedBigInt // CSB 금액
  csb_score         Int?     @db.UnsignedInt   // CSB 관객수
  gonggb_amount     BigInt?  @db.UnsignedBigInt // WB 순 박스오피스 CSB 공급가액
  suryoung_amount   BigInt?  @db.UnsignedBigInt // 단매사 순 박스오피스 정산서 수령액(공급가액)

  @@id([Open, Code, DanmesaCode])
  @@map("wrk_danme_calc")
}

2. 모듈 (danme-calc.module.ts)

이 모듈은 PrismaModule을 가져오고 컨트롤러와 서비스를 설정합니다.

// src/danme-calc/danme-calc.module.ts

import { Module } from '@nestjs/common';
import { DanmeCalcController } from './danme-calc.controller';
import { DanmeCalcService } from './danme-calc.service';
import { PrismaModule } from '../prisma/prisma.module'; // 전역 PrismaModule이 있다고 가정

@Module({
  imports: [PrismaModule],
  controllers: [DanmeCalcController],
  providers: [DanmeCalcService],
})
export class DanmeCalcModule {}

3. 서비스 (danme-calc.service.ts)

서비스는 비즈니스 로직을 처리하고 Prisma 클라이언트와 직접 상호 작용합니다.

// src/danme-calc/danme-calc.service.ts

import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service'; // 경로에 맞게 수정 필요
import { wrk_danme_calc, Prisma } from '@prisma/client';

// 복합 기본 키 타입을 정의하여 사용하기 쉽게 합니다.
type DanmeCalcUniqueKey = {
  Open: string;
  Code: string;
  DanmesaCode: string;
};

@Injectable()
export class DanmeCalcService {
  constructor(private prisma: PrismaService) {}

  // ** 1. 모든 레코드 조회 (READ all) **
  async findAll(): Promise<wrk_danme_calc[]> {
    return this.prisma.wrk_danme_calc.findMany();
  }

  // ** 2. 복합 키로 단일 레코드 조회 (READ one) **
  async findOne(key: DanmeCalcUniqueKey): Promise<wrk_danme_calc | null> {
    return this.prisma.wrk_danme_calc.findUnique({
      where: key, // Prisma는 여기서 복합 키를 자동으로 처리합니다.
    });
  }

  // ** 3. 새 레코드 생성 (CREATE) **
  async create(
    data: Prisma.wrk_danme_calcCreateInput,
  ): Promise<wrk_danme_calc> {
    return this.prisma.wrk_danme_calc.create({
      data,
    });
  }

  // ** 4. 복합 키로 기존 레코드 업데이트 (UPDATE) **
  async update(
    key: DanmeCalcUniqueKey,
    data: Prisma.wrk_danme_calcUpdateInput,
  ): Promise<wrk_danme_calc> {
    return this.prisma.wrk_danme_calc.update({
      where: key,
      data,
    });
  }

  // ** 5. 복합 키로 레코드 삭제 (DELETE) **
  async remove(key: DanmeCalcUniqueKey): Promise<wrk_danme_calc> {
    return this.prisma.wrk_danme_calc.delete({
      where: key,
    });
  }
}

4. 컨트롤러 (danme-calc.controller.ts)

컨트롤러는 들어오는 HTTP 요청을 처리하고, 서비스를 호출하며, 응답을 반환합니다.

// src/danme-calc/danme-calc.controller.ts

import {
  Controller,
  Get,
  Post,
  Body,
  Put,
  Param,
  Delete,
  NotFoundException,
} from '@nestjs/common';
import { DanmeCalcService } from './danme-calc.service';
import { Prisma } from '@prisma/client';

@Controller('danme-calc')
export class DanmeCalcController {
  constructor(private readonly danmeCalcService: DanmeCalcService) {}

  // URL 매개변수에서 복합 키를 추출하는 헬퍼 함수
  private getKeyParams(params: { Open: string; Code: string; DanmesaCode: string }) {
    return {
      Open: params.Open,
      Code: params.Code,
      DanmesaCode: params.DanmesaCode,
    };
  }

  // POST /danme-calc (생성)
  @Post()
  create(@Body() createDanmeCalcDto: Prisma.wrk_danme_calcCreateInput) {
    return this.danmeCalcService.create(createDanmeCalcDto);
  }

  // GET /danme-calc (전체 조회)
  @Get()
  findAll() {
    return this.danmeCalcService.findAll();
  }

  // GET /danme-calc/:Open/:Code/:DanmesaCode (단일 조회)
  // 참고: 키의 세 부분이 모두 URL을 통해 전달되어야 합니다.
  @Get(':Open/:Code/:DanmesaCode')
  async findOne(@Param() params: { Open: string; Code: string; DanmesaCode: string }) {
    const key = this.getKeyParams(params);
    const record = await this.danmeCalcService.findOne(key);

    if (!record) {
      throw new NotFoundException(
        `키 ${key.Open}/${key.Code}/${key.DanmesaCode} 를 가진 레코드를 찾을 수 없습니다.`,
      );
    }
    return record;
  }

  // PUT /danme-calc/:Open/:Code/:DanmesaCode (업데이트)
  @Put(':Open/:Code/:DanmesaCode')
  update(
    @Param() params: { Open: string; Code: string; DanmesaCode: string },
    @Body() updateDanmeCalcDto: Prisma.wrk_danme_calcUpdateInput,
  ) {
    const key = this.getKeyParams(params);
    return this.danmeCalcService.update(key, updateDanmeCalcDto);
  }

  // DELETE /danme-calc/:Open/:Code/:DanmesaCode (삭제)
  @Delete(':Open/:Code/:DanmesaCode')
  remove(@Param() params: { Open: string; Code: string; DanmesaCode: string }) {
    const key = this.getKeyParams(params);
    return this.danmeCalcService.remove(key);
  }
}

🛠️ 다음 단계

  1. Prisma 설정: PrismaService와 PrismaModule이 올바르게 구현되어 ../prisma/ 경로에서 사용 가능한지 확인하세요.
  2. 클라이언트 생성: npx prisma generate를 실행하여 Prisma 클라이언트와 생성된 TypeScript 타입을 업데이트하세요.
  3. 모듈 등록: 루트 AppModule에 DanmeCalcModule을 임포트(Import)하세요.
  4. DTO (Data Transfer Object): 상용 수준의 코드를 위해서는 요청 본문(request body)에 Prisma 생성 타입을 직접 사용하는 대신, class-validator 및 class-transformer를 사용하여 명시적인 DTO(예: CreateDanmeCalcDto, UpdateDanmeCalcDto)를 만드는 것이 좋습니다.

 


Prisma 모듈 설정DTO 파일을 포함하여 필요한 모든 코드를 제공해 드릴게요. 이 파일들을 사용하면 바로 NestJS 프로젝트에 적용할 수 있습니다.


1. Prisma 설정 및 서비스

1-1. prisma/prisma.module.ts (Prisma 모듈)

이 모듈은 PrismaService를 전역적으로 제공할 수 있게 설정합니다.

// src/prisma/prisma.module.ts

import { Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Module({
  providers: [PrismaService],
  exports: [PrismaService], // 다른 모듈에서 사용 가능하도록 export
})
export class PrismaModule {}

1-2. prisma/prisma.service.ts (Prisma 서비스)

NestJS 생명 주기(OnModuleInit, OnModuleDestroy)에 맞게 Prisma 클라이언트 연결을 초기화하고 종료합니다.

// src/prisma/prisma.service.ts

import { Injectable, OnModuleInit, OnModuleDestroy } from '@nestjs/common';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit, OnModuleDestroy {
  async onModuleInit() {
    // 애플리케이션 시작 시 데이터베이스에 연결합니다.
    await this.$connect();
  }

  async onModuleDestroy() {
    // 애플리케이션 종료 시 데이터베이스 연결을 끊습니다.
    await this.$disconnect();
  }
}


2. DTO (Data Transfer Objects) 파일

Prisma 타입을 직접 사용하는 대신, 유효성 검사(class-validator)를 위해 DTO를 생성하는 것이 모범 사례입니다.

2-1. danme-calc.dto.ts

// src/danme-calc/dto/danme-calc.dto.ts

import {
  IsString,
  IsInt,
  IsOptional,
  IsNotEmpty,
  MinLength,
  MaxLength,
  IsPositive,
} from 'class-validator';

// ----------------------------------------------------
// 1. 레코드 생성 시 사용 (POST)
// ----------------------------------------------------

export class CreateDanmeCalcDto {
  // Primary Keys (필수 입력)
  @IsString()
  @IsNotEmpty()
  @MaxLength(6)
  Open: string; // 영화open

  @IsString()
  @IsNotEmpty()
  @MaxLength(2)
  Code: string; // 영화code

  @IsString()
  @IsNotEmpty()
  @MaxLength(1)
  DanmesaCode: string; // 단매사code

  // Optional Fields (선택적 입력)
  @IsOptional()
  @IsInt()
  @IsPositive()
  boxoffice_amount?: bigint; // bigint는 JS에서 Number로 정확히 표현하기 어려워 Prisma에서는 BigInt 타입으로 매핑되지만, DTO에서는 validation만 진행합니다.

  @IsOptional()
  @IsInt()
  @IsPositive()
  boxoffice_score?: number;

  @IsOptional()
  @IsInt()
  @IsPositive()
  csb_amount?: bigint;

  @IsOptional()
  @IsInt()
  @IsPositive()
  csb_score?: number;

  @IsOptional()
  @IsInt()
  @IsPositive()
  gonggb_amount?: bigint;

  @IsOptional()
  @IsInt()
  @IsPositive()
  suryoung_amount?: bigint;
}

// ----------------------------------------------------
// 2. 레코드 수정 시 사용 (PUT/PATCH)
// ----------------------------------------------------
// 모든 필드를 선택적으로 만들고, Primary Key는 제외합니다.
// Primary Key는 URL 파라미터로 받기 때문입니다.

import { PartialType } from '@nestjs/mapped-types';

export class UpdateDanmeCalcDto extends PartialType(CreateDanmeCalcDto) {
  // 복합 키 필드는 상속받지만, PartialType 덕분에 모두 Optional이 됩니다.
  // 이 DTO는 Body 데이터에 사용되며, 키 필드(Open, Code, DanmesaCode)는 제외하고 데이터를 수정합니다.
  // 실제 키는 @Param()으로 전달받습니다.
}


3. 수정된 서비스 및 컨트롤러

3-1. danme-calc.service.ts (수정)

DTO 대신 Prisma의 입력 타입을 사용하도록 유지하되, 필요 함수를 그대로 사용합니다.

// src/danme-calc/danme-calc.service.ts

import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { wrk_danme_calc, Prisma } from '@prisma/client';

// 복합 기본 키 타입
export type DanmeCalcUniqueKey = {
  Open: string;
  Code: string;
  DanmesaCode: string;
};

@Injectable()
export class DanmeCalcService {
  constructor(private prisma: PrismaService) {}

  // 1. 생성 (CREATE)
  async create(
    data: Prisma.wrk_danme_calcCreateInput,
  ): Promise<wrk_danme_calc> {
    return this.prisma.wrk_danme_calc.create({
      data,
    });
  }

  // 2. 전체 조회 (READ all)
  async findAll(): Promise<wrk_danme_calc[]> {
    return this.prisma.wrk_danme_calc.findMany();
  }

  // 3. 단일 조회 (READ one)
  async findOne(key: DanmeCalcUniqueKey): Promise<wrk_danme_calc | null> {
    return this.prisma.wrk_danme_calc.findUnique({
      where: key,
    });
  }

  // 4. 업데이트 (UPDATE)
  async update(
    key: DanmeCalcUniqueKey,
    data: Prisma.wrk_danme_calcUpdateInput,
  ): Promise<wrk_danme_calc> {
    return this.prisma.wrk_danme_calc.update({
      where: key,
      data,
    });
  }

  // 5. 삭제 (DELETE)
  async remove(key: DanmeCalcUniqueKey): Promise<wrk_danme_calc> {
    return this.prisma.wrk_danme_calc.delete({
      where: key,
    });
  }
}

3-2. danme-calc.controller.ts (수정)

DTO를 임포트하고 @Body()에 적용하여 유효성 검사를 수행합니다.

// src/danme-calc/danme-calc.controller.ts

import {
  Controller,
  Get,
  Post,
  Body,
  Put,
  Param,
  Delete,
  NotFoundException,
  UsePipes,
  ValidationPipe,
} from '@nestjs/common';
import { DanmeCalcService, DanmeCalcUniqueKey } from './danme-calc.service';
import { CreateDanmeCalcDto, UpdateDanmeCalcDto } from './dto/danme-calc.dto';

// 컨트롤러 레벨에서 ValidationPipe를 적용하여 DTO 유효성 검사를 활성화합니다.
@Controller('danme-calc')
@UsePipes(new ValidationPipe({ transform: true }))
export class DanmeCalcController {
  constructor(private readonly danmeCalcService: DanmeCalcService) {}

  // URL 매개변수에서 복합 키를 추출하는 헬퍼 함수
  private getKeyParams(params: { Open: string; Code: string; DanmesaCode: string }): DanmeCalcUniqueKey {
    return {
      Open: params.Open,
      Code: params.Code,
      DanmesaCode: params.DanmesaCode,
    };
  }

  // 1. 생성 (POST /danme-calc)
  @Post()
  create(@Body() createDanmeCalcDto: CreateDanmeCalcDto) {
    // DTO를 Prisma CreateInput으로 사용
    return this.danmeCalcService.create(createDanmeCalcDto);
  }

  // 2. 전체 조회 (GET /danme-calc)
  @Get()
  findAll() {
    return this.danmeCalcService.findAll();
  }

  // 3. 단일 조회 (GET /danme-calc/:Open/:Code/:DanmesaCode)
  @Get(':Open/:Code/:DanmesaCode')
  async findOne(@Param() params: { Open: string; Code: string; DanmesaCode: string }) {
    const key = this.getKeyParams(params);
    const record = await this.danmeCalcService.findOne(key);

    if (!record) {
      throw new NotFoundException(
        `키 ${key.Open}/${key.Code}/${key.DanmesaCode} 를 가진 레코드를 찾을 수 없습니다.`,
      );
    }
    return record;
  }

  // 4. 업데이트 (PUT /danme-calc/:Open/:Code/:DanmesaCode)
  @Put(':Open/:Code/:DanmesaCode')
  update(
    @Param() params: { Open: string; Code: string; DanmesaCode: string },
    @Body() updateDanmeCalcDto: UpdateDanmeCalcDto,
  ) {
    const key = this.getKeyParams(params);
    // DTO를 Prisma UpdateInput으로 사용
    return this.danmeCalcService.update(key, updateDanmeCalcDto);
  }

  // 5. 삭제 (DELETE /danme-calc/:Open/:Code/:DanmesaCode)
  @Delete(':Open/:Code/:DanmesaCode')
  remove(@Param() params: { Open: string; Code: string; DanmesaCode: string }) {
    const key = this.getKeyParams(params);
    return this.danmeCalcService.remove(key);
  }
}

🚀 적용 순서 요약

  1. Prisma Schema 확인: schema.prisma에 복합 키가 올바르게 설정되었는지 확인하세요.
  2. Prisma 설치 및 생성:
     
    npm install @prisma/client
    npx prisma generate
    
  3. 파일 배치: 위에 제공된 파일들을 적절한 경로(e.g., src/prisma/, src/danme-calc/, src/danme-calc/dto/)에 배치하세요.
  4. Module 등록: AppModule에 PrismaModule과 DanmeCalcModule을 등록하세요.

이 코드는 복합 키를 효율적으로 처리하고 NestJS의 모범 사례인 DTO 기반 유효성 검사를 포함하고 있습니다.


NestJS 컨트롤러 코드(DanmeCalcController)를 바탕으로 Postman에서 테스트할 수 있는 컬렉션(collection.json) 형태로 JSON을 생성해 드릴 수 있습니다.

이 컬렉션은 기본 URL을 http://localhost:3000/danme-calc (NestJS 기본 포트 3000 가정)로 설정하고, 컨트롤러에 정의된 생성(POST), 전체 조회(GET), 단일 조회(GET), 업데이트(PUT), 삭제(DELETE) 엔드포인트에 대한 요청 예시를 포함합니다.


🏗️ Postman 컬렉션 JSON

다음 JSON 코드를 Postman에서 Import 기능을 사용하여 불러오시면 됩니다.

danme-calc_collection.json

{
  "info": {
    "_postman_id": "YOUR_UNIQUE_POSTMAN_ID",
    "name": "Danme Calc API",
    "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
  },
  "item": [
    {
      "name": "Create wrk_danme_calc (POST)",
      "request": {
        "method": "POST",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n    \"Open\": \"TEST_OPEN_KEY\",\n    \"Code\": \"TEST_CODE\",\n    \"DanmesaCode\": \"TEST_DANMESA\",\n    \"DanmeName\": \"단메 이름 예시\",\n    \"CalculateValue\": 1234.56,\n    \"UsedValue\": 100.00,\n    \"CalculatedAt\": \"2025-01-01T10:00:00Z\"\n}"
        },
        "url": {
          "raw": "{{baseUrl}}/danme-calc",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "danme-calc"
          ]
        },
        "description": "새로운 단메 계산 레코드를 생성합니다. 복합 키 필드(Open, Code, DanmesaCode)를 포함해야 합니다."
      },
      "response": []
    },
    {
      "name": "Find All wrk_danme_calc (GET)",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/danme-calc",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "danme-calc"
          ]
        },
        "description": "모든 단메 계산 레코드를 조회합니다."
      },
      "response": []
    },
    {
      "name": "Find One wrk_danme_calc (GET)",
      "request": {
        "method": "GET",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/danme-calc/TEST_OPEN_KEY/TEST_CODE/TEST_DANMESA",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "danme-calc",
            "TEST_OPEN_KEY",
            "TEST_CODE",
            "TEST_DANMESA"
          ]
        },
        "description": "복합 키를 사용하여 특정 단메 계산 레코드를 조회합니다. 경로 매개변수 순서는 Open, Code, DanmesaCode 입니다."
      },
      "response": []
    },
    {
      "name": "Update wrk_danme_calc (PUT)",
      "request": {
        "method": "PUT",
        "header": [
          {
            "key": "Content-Type",
            "value": "application/json"
          }
        ],
        "body": {
          "mode": "raw",
          "raw": "{\n    \"DanmeName\": \"업데이트된 단메 이름\",\n    \"CalculateValue\": 9999.99\n}"
        },
        "url": {
          "raw": "{{baseUrl}}/danme-calc/TEST_OPEN_KEY/TEST_CODE/TEST_DANMESA",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "danme-calc",
            "TEST_OPEN_KEY",
            "TEST_CODE",
            "TEST_DANMESA"
          ]
        },
        "description": "복합 키를 사용하여 특정 단메 계산 레코드를 업데이트합니다."
      },
      "response": []
    },
    {
      "name": "Delete wrk_danme_calc (DELETE)",
      "request": {
        "method": "DELETE",
        "header": [],
        "url": {
          "raw": "{{baseUrl}}/danme-calc/TEST_OPEN_KEY/TEST_CODE/TEST_DANMESA",
          "host": [
            "{{baseUrl}}"
          ],
          "path": [
            "danme-calc",
            "TEST_OPEN_KEY",
            "TEST_CODE",
            "TEST_DANMESA"
          ]
        },
        "description": "복합 키를 사용하여 특정 단메 계산 레코드를 삭제합니다."
      },
      "response": []
    }
  ],
  "variable": [
    {
      "key": "baseUrl",
      "value": "http://localhost:3000",
      "type": "string"
    }
  ]
}

📝 사용 방법 안내

1. 환경 설정

  1. Postman의 오른쪽 상단에 있는 Environments 드롭다운 메뉴를 클릭하고 Manage Environments를 선택합니다.
  2. 새로운 환경을 추가하고 이름을 지정합니다 (예: NestJS Local).
  3. 다음 변수를 추가합니다:
    • Variable: baseUrl
    • Initial Value: http://localhost:3000 (NestJS 서버가 실행 중인 주소와 포트)
    • Current Value: http://localhost:3000
  4. 환경을 저장하고 드롭다운에서 방금 생성한 환경을 선택합니다.

2. 컬렉션 가져오기

  1. Postman 왼쪽 상단의 Import 버튼을 클릭합니다.
  2. 위의 JSON 코드를 복사하여 Raw Text 탭에 붙여넣거나, 파일로 저장하여 File 탭에서 업로드합니다.
  3. 컬렉션이 성공적으로 가져와지면, 왼쪽 사이드바의 Collections 탭에서 Danme Calc API를 확인할 수 있습니다.

3. 테스트 시 유의사항

  • 키 값: Find One, Update, Delete 요청 URL에 포함된 TEST_OPEN_KEY, TEST_CODE, TEST_DANMESA 값은 Create 요청 시 사용한 값과 일치해야 성공적으로 동작합니다.
  • POST/PUT Body: wrk_danme_calc 테이블의 실제 스키마에 맞게 필드 이름과 데이터 타입을 조정해야 할 수 있습니다. 위에 제공된 예시는 일반적인 데이터베이스 필드를 가정한 것입니다.
  • 실행 순서: 일반적으로 Create $\rightarrow$ Find All $\rightarrow$ Find One $\rightarrow$ Update $\rightarrow$ Delete 순서로 테스트하는 것이 좋습니다.
728x90