JSON 파일에 저장된 메세지를 검색하고 저장하는 간단 프로젝트 구현
조건 : ① 저장한 모든 메세지를 나열(GET, :_id) ② 메세지 검색(GET) ③ 메세지 생성(POST)
→ 3가지 경로가 필요함
cmd 창이나 VSCode의 터미널에서 다음의 코드를 입력한다
npm install -g @nestjs/cli
(만약 설치되지 않으면 맨 앞에 sudo를 붙여서 설치한다)
Nest CLI를 전역으로 설치하는 이유?
개발자가 여러 프로젝트에서 동일한 cli도구를 사용할 수 있어 프로젝트간의 일관성을 유지 할 수 있음. 편의성과 생산성을 높이기 위함
1. cmd 창에 다음의 명령어 입력
nest new <파일명>
2. npm, yarn 중 선택
나의 경우 npm을 선택했다.
3. 필요없는 파일은 삭제해도 된다
typescript의 경우 이미 오류 포착에 탁월하므로 eslint는 딱히 필요하지 않는 경우도 많음.
나의 경우, 모듈/컨트롤러/서비스 등의 모든 파일을 삭제하고 eslint를 비활성화했다.
4. cmd 창이나 VSCode 터미널에 명령어 입력
npm run start:dev
nest generate module <파일명>
파일명에 모듈이라는 단어를 포함하면 자동으로 생성되는 이름에 중복되므로 쓰지 말 것!
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 데코레이터라고 함 (각 메서드에서 부분적으로 적용되므로)
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로서 다 적용이 되고있음을 확인
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상태 코드 반환하기 때문에 비동기 처리가 들어감!
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;
}
}
service, repository 수정
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의 방식을 따름