View

TIL | NestJS_URI_parameter_받아오기

Melody:) 2021. 10. 17. 19:23

목표는 1번 Sentence의 Voice파일 1개를 생성하는 기능구현을 하는 것이었다.

먼저 prisma를 통하여 다음과 같이 모델링을 하였다.

(User는 다른 데이터베이스에 들어가야하지만, 아직  같은 데이터 베이스 안에서 데이터를 넣는 것부터도 시도해보지 않은 상태여서,

데이터베이스 분리는 나중에 해보기로 결정하였다. )

// This is your Prisma schema file,
// learn more about it in the docs: https://pris.ly/d/prisma-schema

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}

generator client {
  provider = "prisma-client-js"
}

model Voice {
    id	          Int           @default(autoincrement()) @id
    fileSize	    Decimal       @db.Decimal(8,3)           
    url	          String        @db.VarChar(500) 
    user	      User            @relation(fields: [userId], references: [id])       
    userId	      Int           
    sentence      Sentence      @relation(fields: [sentenceId], references: [id])
    sentenceId    Int      
    dateOfCreated DateTime    @default(now())
    dateOfUpdated DateTime?   @updatedAt
    voiceStatus   VoiceStatus[]

    @@map("voice")
}

model VoiceStatus {
    id      	    Int       @default(autoincrement()) @id
    voice	        Voice     @relation(fields: [voiceId], references: [id])
    voiceId	        Int       
    downloadCount	Int       @default(0)    
    clientId   	    Int?      
    verifyStatus 	String    @db.VarChar(50) @default("None")
    user	        User?     @relation(fields: [userId], references: [id])       
    userId	        Int?           
    playCount	    Int       @default(0)    
    dateOfCreated	DateTime  @db.Timestamptz(3) @default(now())
    dateOfUpdated	DateTime? @db.Timestamptz(3) @updatedAt
    
    @@map("voice_statuse")
}

model Script {
    id	          Int            @default(autoincrement()) @id
    title	      String         @db.VarChar(50) @unique
    url	          String         @db.VarChar(500)
    fileSize	    Decimal       @db.Decimal(8,3)
    remarks       String         @db.VarChar(1000) 
    client        Client?        @relation(fields: [clientId], references:[id])
    clientId	  Int?  
    dateOfCreated DateTime       @db.Timestamptz(3) @default(now())
    dateOfUpdated DateTime?      @db.Timestamptz(3) @updatedAt
    scriptStatus  ScriptStatus[]
    sentence      Sentence[]

    @@map("script")
}

model ScriptStatus {
    id	                 Int       @default(autoincrement()) @id
    script	             Script    @relation(fields: [scriptId], references: [id])
    scriptId	         Int       @db.Integer
    post	             Boolean   @default(false)
    deadline	         Boolean   @default(false)
    downloadCount	     Int       @default(0)
    client               Client?   @relation(fields: [clientId], references:[id])
    clientId	         Int?  
    downloadMax         Int?      @default(1)
    downloadPreiodFrom DateTime? @db.Date    
    downloadPreiodTo   DateTime? @db.Date
    dateOfCreated	     DateTime  @db.Timestamptz(3) @default(now())
    dateOfUpdated	     DateTime? @db.Timestamptz(3) @updatedAt
    
    @@map("script_status")
}

model SentenceStatus {
    id	               Int      @default(autoincrement()) @id
    sentence           Sentence @relation(fields: [sentenceId], references: [id])
    sentenceId         Int      
    deadlineStatus Boolean      @default(false)
    dateOfCreated  DateTime     @db.Timestamptz(3) @default(now())
    dateOfUpdated  DateTime?    @db.Timestamptz(3) @updatedAt
    
    @@map("sentence_status")
}

model Sentence {
    id             Int       @default(autoincrement()) @id
    script	       Script    @relation(fields: [scriptId], references: [id])
    scriptId	   Int      
    context	       String    @db.VarChar(5000)
    language       String    @db.VarChar(50)
    age	           Int?      
    gender         String?   @db.VarChar(50)
    dateOfCreated  DateTime  @db.Timestamptz(3) @default(now())
    dateOfUpdated  DateTime? @db.Timestamptz(3) @updatedAt
    voice	       Voice[]    
    sentenceStatus SentenceStatus[]

    @@map("sentence")
}

model Client {
  id            Int      @id @default(autoincrement())
  loginId       String   @unique
  clientName    String
  ramarks       String?  @db.Text
  dateOfCreated DateTime @default(now())
  dateOfUpdated DateTime @updatedAt
  password      String
  role          Role     @default(CLIENT)
  script        Script[]
  scriptStatus  ScriptStatus[]

  @@map("client")
}

model User {
  id            Int        @id @default(autoincrement())
  loginId       String     @unique
  firstName     String
  middleName    String?
  lastName      String
  gender        Gender?
  dateOfCreated DateTime   @default(now())
  dateOdUpdated DateTime   @updatedAt
  status        Boolean    @default(false)
  birthday      String
  password      String
  role          Role?       @default(USER)
  languages     String
  voice         Voice[]
  voiceStatus   VoiceStatus[]

  @@map("user")
}

enum Gender {
  MALE
  FEMALE
  NEUTRAL
}

enum Role {
  ADMIN
  CLIENT
  USER
}

dateOfUpdated필드의 ?는 update는 나중에 되므로 처음 데이터가 생성될때 null값을 허용한다는 의미에서 붙였다.

모델링 후 다음과 같이 VoiceDTO를 작성하였다.

export class VoiceDTO {
    @IsOptional()
    @IsNumber()
    id	   
    
    @IsOptional()
    @IsNumber()
    fileSize	   
    
    @IsUrl()
    @IsString()
    url	          
    
    @IsNumber()
    user	        
    
    @IsOptional()
    @IsNumber()
    sentence
   
    @IsDate()
    dateOfCreated	
    
    @IsOptional()
    @IsDateString()
    dateOfUpdated
}

DTO작성할때,    dateOfCreated,   dateOfUpdated 를 작성한 것을 잘 봐두자...

이후 Voice폴더 안에 controller, service, module을 생성하였으며,

main.ts에서 먼저 app.module을 통해 접속이 이루어지고, app.module에 있는 controller를 통해 service의 코드들이 작동하는 원리이다.  먼저 연습을 위해 아래와 같이 app.module을 작성하였고, voice.module로 endpoint가 잘 접속되는지 확인해보고자 하였다.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { VoiceController } from './voice/voice.controller';
import { VoiceModule } from './voice/voice.module';
import { ScriptController } from './script/script.controller';
import { ScriptModule } from './script/script.module';
import { PrismaService } from './prisma/prisma.service';
import { PrismaModule } from './prisma/prisma.module';
import { VoiceService } from './voice/voice.service';
import { ScriptService } from './script/script.service';

@Module({
  imports: [VoiceModule, ScriptModule, PrismaModule],
  controllers: [AppController, VoiceController, ScriptController],
  providers: [AppService, VoiceService, PrismaService, ScriptService],
})
export class AppModule {}

AppController와 AppService는 다음과 같이 비워두었고,

import { 
  Controller,
  Get,
  Param,
  Post,
  Body,
  Put,
  Delete, 
} from '@nestjs/common';
import { VoiceService } from './voice/voice.service';
import { ScriptService } from './script/script.service';
import { Voice as VoiceModel, Script as ScriptModel } from '@prisma/client';
import { user } from './dto/user.dto';



@Controller('')
export class AppController {

  constructor(
    private readonly scriptService : ScriptService,
    private readonly voiceService  : VoiceService,
  ) {}
}
import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getHello(): string {
    return 'Hello World!';
  }
}

VoiceController에 부터 코드 작성을 시작하였다. 

구현할 endpoint는 http://localhost:3000/user/voice/submit?stentence_id=1이며, 필요한 parameter는 stentence_id라는 파라미터 이다.

url의 파라미터를 받아야하는데, 어떻게 받을지 먼저 고민을 하였고, 처음에 javascript와 typescript에서 url의 파라미터를 받는 방법을 찾아보다가, 이와 다른 표현방식을 사용한 블로그의 코드를 발견하게 되어, 이 방식은 NestJS에서 파라미터를 방는 방법임을 알게되었다.

그리하여, NestJS공식문서를 통해 controller에서 url의 파라미터를 받는 방법을 찾아보았고, 

https://docs.nestjs.com/controllers

쿼리 파라미터는 @Query('')로 , path 파라미터는 @Param('')으로, body의 데이터는 @Body로 받게 되는 것을 알게 되었다.
자 그럼 이제 방법을 알았으니, 적용해볼 차례이다. 일단 내가 작성한 아래의 코드로 데이터가 잘 받아지는지를 먼저 확인하기 위해 

import { Body, Controller, Post, Param, Query } from '@nestjs/common';
import { VoiceDTO } from '../dto/voice.dto';
import { VoiceService } from './voice.service';
import { Voice } from '@prisma/client';

@Controller('user/voice')
export class VoiceController {
    constructor(private readonly voiceServicve: VoiceService) {}

    @Post('submit')
    createVoice(@Query('sentence_id') sentence_id: string, @Body() voiceData:VoiceDTO):Promise<boolean> { 
        return this.voiceServicve.createVoice(sentence_id, voiceData);
    }
}

VoiceService를 아래와 같은 방법으로 작성해 보았다.

아래 코드는 단순히 @Body를 통해 받은 데이터를 콘솔에 찍어주는 함수이다.

import { Injectable } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { VoiceDTO } from '../dto/voice.dto';
import {
  Voice,
  Prisma
} from '@prisma/client';

@Injectable()
export class VoiceService {

    constructor(private prisma: PrismaService) {}

    async createVoice(id: string, voiceData: Prisma.VoiceCreateInput): Promise<boolean> {
        
      console.log(voiceData)
      return true;
    }
}

이전에 작성해 놓은 아래 VoiceDto를 기반으로 테스트를 진행하였다.

export class VoiceDTO {
    @IsOptional()
    @IsNumber()
    id	   
    
    @IsOptional()
    @IsNumber()
    fileSize	   
    
    @IsUrl()
    @IsString()
    url	          
    
    @IsNumber()
    user	        
    
    @IsOptional()
    @IsNumber()
    sentence
   
    @IsDate()
    dateOfCreated	
    
    @IsOptional()
    @IsDateString()
    dateOfUpdated
}

테스트용 바디는 아래와 같다.

{
    "fileSize"	           : 3214,  
    "url"	               : "http://adfsdff.com",            
    "user"	               : 1   
}

하지만 결과는 아래와 같았다...ㅠㅠ

"dateOfCreated must be a Date instance" : dateOfCreated가 date객체어야 한다는 말인데,
dateOfCreated에 문제라는 말에 처음에는 model의  dateOfCreated의 타입에 null을 허용해서 DateTime?도 줘보았다.
또한, 바디안에 dateOfCreated도 데이터로 넣어 전송시켜봤으나, 해결되지 않았다.

그리고 본론으로 돌아와서........

내가 짜 놓은 모델은 dateOfCreated가 default(now())로 설정되어있어서 dateOfCreated값이 body로 전송될 필요가 없다는 것을 깨달은 뒤 그렇다면, 데이터 전송객체인 DTO가 문제였다는 것을 깨닫게 되었고, 아래와 같이 필드값이 생성되는 동시에 자동으로 default가 생성되는 dateOfCreated을 제거해 보기로 하였다.

그렇다면, 왜 dateOfUpdated에 대해서는 오류가 안떴을까???

그 이유는 바로 아래와 같이 해당값이 들어와도 되고, 안들어와도 되는 @IsOptional()을 걸어놓았기 때문이다.. ^^

export class VoiceDTO {
    @IsOptional()
    @IsNumber()
    id	   
    
    @IsOptional()
    @IsNumber()
    fileSize	   
    
    @IsUrl()
    @IsString()
    url	          
    
    @IsNumber()
    user	        
    
    @IsOptional()
    @IsNumber()
    sentence
   
    @IsDate()
    dateOfCreated	
    
    @IsOptional()
    @IsDateString()
    dateOfUpdated
}

 

그리하여 바로 즉시 VoiceDTO에서 dateOfCreated, dateOfUpdated를 삭제해주었다.

그리고 요청을 보낸 결과 아래과 같이 예쁜 True를 얻을 수 있었다.

그리고 덤으로 Body에 담아 보낸 데이터들이 콘솔에도 예쁘게 찍힌 것을 볼수있었다!!!!!!!!

여기서 뽀인트는
Body를 통해 받은 데이터들이 VoiceDTO의 형식을 잘 지킨 값들이라고 앞쪽에 VoiceDTO로 표시해주고있다는 것이다..ㅎㅎ

내가 놓쳤던 것은 model을 보고 DTO를 작성하다보니,

실제로 body로 넘겨받지 않는 데이터에 대해서도 DTO가 작성되었다는 것이었다.ㅠㅠ

이를 유의하여 앞으로의 DTO들도 잘 짜야겠다...!!

자, 그럼 이제 다음 블로깅에서는 받아온 값들을  Voice 테이블에 넣어보도록 하자!

 

 

 


body의 값을 받아오는 또다른 방법

@Controller('user/voice')
export class VoiceController {
    constructor(private readonly voiceServicve: VoiceService) {}

    @Post('submit')
    createVoice(@Body() voiceData:{fileSize, url} ): boolean {
        const {fileSize, url} = voiceData;

        return this.voiceServicve.createVoice({
            fileSize,
            url,
            user:{connect : {id : 1}},
            sentence :{connect : {id : 1}}
        });
    }

}
@Injectable()
export class VoiceService {

    constructor(private prisma: PrismaService) {}
    
    createVoice(voiceData: Prisma.VoiceCreateInput): boolean {
      console.log(voiceData)
      this.prisma.voice.create({data:voiceData});
      
      return true;
    }
}

중요한 것은 DTO와 모두 변수를 일치시켜야한다.

Share Link
reply
«   2024/10   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30 31