[Nest, Typescript] Generating Projects with Nest CLI

clo승·2023년 7월 21일
0

JSON 파일에 저장된 메세지를 검색하고 저장하는 간단 프로젝트 구현

조건 : ① 저장한 모든 메세지를 나열(GET, :_id) ② 메세지 검색(GET) ③ 메세지 생성(POST)
→ 3가지 경로가 필요함


Nest CLI 설치

cmd 창이나 VSCode의 터미널에서 다음의 코드를 입력한다

npm install -g @nestjs/cli
(만약 설치되지 않으면 맨 앞에 sudo를 붙여서 설치한다)

Nest CLI를 전역으로 설치하는 이유?
개발자가 여러 프로젝트에서 동일한 cli도구를 사용할 수 있어 프로젝트간의 일관성을 유지 할 수 있음. 편의성과 생산성을 높이기 위함


Generate Project

1. cmd 창에 다음의 명령어 입력

nest new <파일명>

2. npm, yarn 중 선택
나의 경우 npm을 선택했다.

3. 필요없는 파일은 삭제해도 된다
typescript의 경우 이미 오류 포착에 탁월하므로 eslint는 딱히 필요하지 않는 경우도 많음.
나의 경우, 모듈/컨트롤러/서비스 등의 모든 파일을 삭제하고 eslint를 비활성화했다.

4. cmd 창이나 VSCode 터미널에 명령어 입력

npm run start:dev

generate module

nest generate module <파일명> 
파일명에 모듈이라는 단어를 포함하면 자동으로 생성되는 이름에 중복되므로 쓰지 말 것!

generate controller

nest generate controller <폴더명>/<파일명>
ex. nest generate controller messages/messages 

adding routing (API 생성)

아무것도 없는 API는 status 200 반환해야함! (http://localhost:3000/messages - get)
→ postman.com이나 REST Clinet로 확인할 것!

import { Controller, Get, Post, Body, Param } from '@nestjs/common';
@Controller('messages')
export class MessagesController {
  @Get()
  listMessages() {}
  @Post()
  createMessage(@Body() body: any) {
    console.log(body);
  }
  @Get('/:id')
  getMessage(@Param('id') id: string) {
    console.log(id);
  }
}

Controller 는 class 데코레이터
GET, POST 는 method 데코레이터
Body, Param 은 argument 데코레이터라고 함 (각 메서드에서 부분적으로 적용되므로)


generate pipe - validate check

1. post 요청 시 Pipe가 필요함. Controller에 요청이 도착하기 전 Pipe에서 처리하게 됨
메세지의 길이가 너무 길거나 짧은지, 문자열인지 등 확인
→ 원래 작업중인 폴더에서 상위 폴더로 이동

2. main.ts 파일 내용 변경 - global validation

import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';   // 추가
import { MessagesModule } from './messages/messages.module';

async function bootstrap() {
  const app = await NestFactory.create(MessagesModule);
  app.useGlobalPipes(new ValidationPipe());   // 추가 (global로 모든 수신에 대해 검사. 단일로도 가능!)
  await app.listen(3000);
}
bootstrap();


이 에러는 class-validator와 class-transformer라는 것을 설치하면 해결된다.

npm i --save class-validator class-transformer

3. DTO 설정 - class validation

1. src > messages > dtos > create-messages.dto.ts 생성
2. create-messages.dto.ts에 아래의 코드 작성

import { IsString } from 'class-validator';

export class CreateMessageDto {
  @IsString()   // 유효성 검사기
  content: string;
}

3. messages.controller.ts 수정

import { Controller, Get, Post, Body, Param } from '@nestjs/common';
import { CreateMessageDto } from './dtos/create-message.dto';   // 추가

@Controller('messages')
export class MessagesController {
  @Get()
  listMessages() {}

  @Post()
  createMessage(@Body() body: CreateMessageDto) {   // body:any 에서 변경
    console.log(body);
  }

  @Get('/:id')
  getMessage(@Param('id') id: string) {
    console.log(id);
  }
}

DTO (Data Transfer Object)

  • 데이터 전송에 사용되는 객체로, 주로 서비스간 데이터 교환을 위해 사용
  • 데이터를 전송할 때 데이터의 형식과 구조를 명확하게 정의
  • 서로 다른 형식의 데이터를 변환하는 작업을 쉽게 수행 (서로 다른 모듈 / 서비스간의 데이터 통신을 효율적으로 처리)
  • 타입스크립트에서는 변수에 TYPE을 꼭 지정해주는데 DTO는 내가 그 타입을 만들 수 있고, 데이터 전송 시 포함 여부도 결정할 수 있다!!

controller에서 body: String으로 안하고 CreateMessageDto라는 DTO를 만든 이유?

  • 데이터 유효성 검사 : class-validator 라이브러리를 사용하여 데이터의 유효성을 검사하는 기능 제공
  • 확장성 : 필요에 따라 DTO에 다른 필드를 추가해서 content타입에 적용시킬 수 있음(컨트롤러와 서비스 사이에서 더 복잡한 데이터 송수신 가능)
  • 코드 가독성 : 저렇게 선언함으로써 해당 메소드가 어떤 데이터를 필요로 하는지 바로 이해 가능
  • 재사용성 : DTO는 서로다른 컨트롤러 메서드에도 재사용 할 수 있음
  • 유지보수성 : 데이터 형식이 바뀌거나 추가되는 경우 DTO폴더라는 한 곳에서만 수정하면 됨

TS를 사용하면 JS에 어떤 타입 정보도 보존되지 않는거 아니야??

  • tsconfig.json에서 설정한 emitDecoratorMetadata : true로 옵션을 설정하였기 때문에 아주 적은 양의 형식 주석(CreateMessageDto와 같은 정보)을 Javascript로 변환시 허용해 줌
  • dist 폴더에서 자바스크립트로 변환된 타입스크립트의 코드를 보면
__metadata("design:paramtypes", [create_message_dto_1.CreateMessageDto])

로 back단에서는 metaData로서 다 적용이 되고있음을 확인


generate repository

1. messages.json 파일 생성
create 했을 때 key : value 를 저장할 파일

예시 코드

{
    "12":{
        "content":"hi there!",
        "id":12
    },
    "13":{
        "content":"hi there!",
        "id":13
    }
}

2. repository.ts 파일 생성

readfile로 정보 가져와서 JSON.parse로 컴퓨터가 읽을 수 있게 파싱처리하는 것이 주된 work

JSON.parse() - JSON 문자열을 인자로 받고 JavaScript 객체로 변환하여 반환
↔ JSON.stringify() - JavaScript 객체를 JSON 문자열로 변환

예시 코드

import { Injectable } from '@nestjs/common';
import { readFile, writeFile } from 'fs/promises';

@Injectable()
export class MessagesRepository {   // 일반적으로 findone, findall, create가 필요함
  async findOne(id: string) {
    const contents = await readFile('messages.json', 'utf8');
    const messages = JSON.parse(contents);

    return messages[id];
  }

  async findAll() {
    const contents = await readFile('messages.json', 'utf8');
    const messages = JSON.parse(contents);

    return messages;
  }

  async create(content: string) {
    const contents = await readFile('messages.json', 'utf8');
    const messages = JSON.parse(contents);

    const id = Math.floor(Math.random() * 999);

    messages[id] = { id, content };

    await writeFile('messages.json', JSON.stringify(messages));
  }
}

async/ await 비동기처리하지 않으면 findOne 등 을 통해 찾은 결과를 보기도 전에 message에 프로미스 객체가 할당되어 404코드를 반환하지 않고, 200상태 코드 반환하기 때문에 비동기 처리가 들어감!


generate service

1. service.ts 파일 생성

예시 코드

import { Injectable } from '@nestjs/common';
import { MessagesRepository } from './messages.repository';

@Injectable()
export class MessagesService {
  constructor(public messagesRepo: MessagesRepository) {}

  findOne(id: string) {
    return this.messagesRepo.findOne(id);
  }

  findAll() {
    return this.messagesRepo.findAll();
  }

  create(content: string) {
    return this.messagesRepo.create(content);
  }
}

2. 필요에 따라 controller.ts 파일 수정

예시 코드

import {
  Controller,
  Get,
  Post,
  Body,
  Param,
  NotFoundException,
} from '@nestjs/common';   // 에러표현때문에 추가됨
import { CreateMessageDto } from './dtos/create-message.dto';
import { MessagesService } from './messages.service';   // 수정

@Controller('messages')
export class MessagesController {
  constructor(public messagesService: MessagesService) {}   // public을 쓰면 인수가 class의 속성으로 자동할당된다

  @Get()
  listMessages() {   // 내용기입
    return this.messagesService.findAll();
  }

  @Post()
  createMessage(@Body() body: CreateMessageDto) {   // 내용기입
    return this.messagesService.create(body.content);   // body는 content 속성을 가져와야함
  }

  @Get('/:id')
  async getMessage(@Param('id') id: string) {   // 내용기입
    const message = await this.messagesService.findOne(id);

    if (!message) {
      throw new NotFoundException('message not found');
    }

    return message;
  }
}

DI containter (종속성 작성 - injectable)

service, repository 수정

  • service, repository를 DI container에 담아야하기 때문.
  • controller는 앞단이므로 필요 없음!
  • 수정한 코드는 위 코드에 반영되어 있음

Module.ts 수정

import { Module } from '@nestjs/common';  
import { MessagesController } from './messages.controller';
import { MessagesService } from './messages.service';   // 수정
import { MessagesRepository } from './messages.repository';   // 수정

@Module({
  controllers: [MessagesController],
  providers: [MessagesService, MessagesRepository],   // 수정
})
export class MessagesModule {}

코드 동기화 공부

typescript는 best를 따르기 약간 어려움이 존재함! nest에서는 주로 better의 방식을 따름

profile
무엇이든 해내는 사람

0개의 댓글