본문 바로가기
내일배움 강의/강의-NestJS

3주차 실습

by GREEN나무 2025. 1. 21.
728x90

 

환경설정 - NestJS는 npm으로 써야함




Nest.js 게시판 프로젝트 생성
nest new nestjs_board #npm 선택택

Nest.js 게시판 프로젝트 디렉토리로 이동
cd nestjs_board

@nestjs/mapped-types, class-validator 패키지를 설치
npm i @nestjs/mapped-types class-validator

lodash 패키지를 설치
npm i lodash @types/lodash

Visual Studio Code로 게시판 프로젝트 열기
code .

tsconfig.json에 추가
  "esModuleInterop": true // 추가. ES6 모듈 사양을 준수하여 CommonJS 모듈을 가져오기

prettier 설치
npm install --save-dev prettier

.prettierrc파일 추가
{
  "semi": true,               
  "singleQuote": true,         
  "tabWidth": 2,               
  "useTabs": false,         
  "printWidth": 80,          
  "trailingComma": "es5",      
  "bracketSpacing": true,
  "jsxBracketSameLine": false, 
  "arrowParens": "always"      
}

package.json에 추가(자동으로 추가되지만 확인해보자)
"format": "prettier --write \"src/**/*.ts\" \"test/**/*.ts\"",


nestjs_board프로젝트의 ./src 폴더로 이동
cd src 

게시판 뼈대 한 번에 완성하기  nest g resource 폴더이름
nest g resource post # REST API, Y

결과
  PS V:\Sparta\memoNote\github\D1\dailyCoding\NESTJS\3W> cd src
  PS V:\Sparta\memoNote\github\D1\dailyCoding\NESTJS\3W\src> nest g resource post
  ? What transport layer do you use? REST API
  ? Would you like to generate CRUD entry points? Yes
  CREATE post/post.controller.ts (917 bytes)
  CREATE post/post.controller.spec.ts (576 bytes)
  CREATE post/post.module.ts (250 bytes)
  CREATE post/post.service.ts (633 bytes)
  CREATE post/post.service.spec.ts (464 bytes)
  CREATE post/dto/create-post.dto.ts (31 bytes)
  CREATE post/dto/update-post.dto.ts (173 bytes)
  CREATE post/entities/post.entity.ts (22 bytes)

빌드
npm run build

실행
npm run start

이렇게 뜨면 정상작동
PS V:\Sparta\memoNote\github\D1\dailyCoding\NESTJS\3W\nestjs_board\src> npm run start

> nestjs_board@0.0.1 start
> nest start

[Nest] 23560  - 2025. 01. 21. 오후 3:31:03     LOG [NestFactory] Starting Nest application...
[Nest] 23560  - 2025. 01. 21. 오후 3:31:03     LOG [InstanceLoader] AppModule dependencies initialized +6ms
[Nest] 23560  - 2025. 01. 21. 오후 3:31:03     LOG [InstanceLoader] PostModule dependencies initialized +0ms
[Nest] 23560  - 2025. 01. 21. 오후 3:31:03     LOG [RoutesResolver] AppController {/}: +3ms
[Nest] 23560  - 2025. 01. 21. 오후 3:31:03     LOG [RouterExplorer] Mapped {/, GET} route +1ms
[Nest] 23560  - 2025. 01. 21. 오후 3:31:03     LOG [RoutesResolver] PostController {/post}: +0ms
[Nest] 23560  - 2025. 01. 21. 오후 3:31:03     LOG [RouterExplorer] Mapped {/post, POST} route +1ms
[Nest] 23560  - 2025. 01. 21. 오후 3:31:03     LOG [RouterExplorer] Mapped {/post, GET} route +0ms
[Nest] 23560  - 2025. 01. 21. 오후 3:31:03     LOG [RouterExplorer] Mapped {/post/:id, GET} route +1ms
[Nest] 23560  - 2025. 01. 21. 오후 3:31:03     LOG [RouterExplorer] Mapped {/post/:id, PATCH} route +0ms
[Nest] 23560  - 2025. 01. 21. 오후 3:31:03     LOG [RouterExplorer] Mapped {/post/:id, DELETE} route +1ms
[Nest] 23560  - 2025. 01. 21. 오후 3:31:03     LOG [NestApplication] Nest application successfully started +1ms


 

 

클라이언트와 서버의 데이터 송수신은 DTO

 

서비스에서
BadRequestException 던지기
throw new BadRequestException 

 

// post.service.ts
import _ from 'lodash'; // 추가
import { BadRequestException, Injectable, NotFoundException } from '@nestjs/common';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { RemovePostDto } from './dto/remove-post.dto';

@Injectable()
export class PostService {
  private articles: { id: number; title: string; content: string }[] = [];
  // 각 배열은  { id: 11; title: '열한번째 포스트'; content: '이제 열한번째 포스트. 한참남았다' } 이런 형태
  private articlePasswords = new Map<number, number>();

  create(createPostDto: CreatePostDto) {
    const { title, content, password } = createPostDto;
    const id = this.articles.length + 1;// 프리즈마가 아니라 그런지 id를 지정하네

    this.articles.push({ id, title, content }); // 배열에 추가
    this.articlePasswords.set(id, password); // articlePasswords배열은 id값이 키고, password가 값으로 추가 

    return { id };
  }

  //  게시물 ID와 게시물 제목만 리턴
  findAll() {
    return this.articles.map(({id,title}) => ({id,title}))
    // this.articles 배열의 각 요소를 특정 형식으로 변환
  }

  findOne(id: number) {
    return this.articles.find((article)=>article.id===id);
  }

  update(id: number, updatePostDto: UpdatePostDto) {
    const { content, password } = updatePostDto;
    const article = this.articles.find((article) => article.id === id);

    // 받은 id와 일치하는 값이 있는지 확인
    if (_.isNil(article)) {  // _.isNil : 널이 아니면 false 반환
      throw new NotFoundException('게시물을 찾을 수 없습니다.');// NotFound는 404.   ctrl + '.' -> "@nestjs/common"에서 가져오기 업데이트
          }

    // id로 비번찾고 받은 비번과 일치하는지 확인
    const articlePassword = this.articlePasswords.get(id);
    if (!_.isNil(articlePassword) && articlePassword !== password) {
      throw new NotFoundException('비밀번호가 일치하지 않습니다.'); // 404
    }

    // id와 일치하는 기사와 비번이 있으면 내용 수정
    article.content = content;
  }

  remove(id: number, deletePostDto: RemovePostDto) {
      const articleIndex = this.articles.findIndex(
      (article) => article.id === id,
      );

      if (articleIndex === -1) {
        throw new NotFoundException('게시물을 찾을 수 없습니다.');
      }

    const articlePassword = this.articlePasswords.get(id);
    if (
      !_.isNil(articlePassword) &&
      articlePassword !== deletePostDto.password
    ) {
      throw new NotFoundException('비밀번호가 일치하지 않습니다.');
    }

    this.articles.splice(articleIndex, 1);
  }
}

 

// nestjs_board\src\post\dto\create-post.dto.ts
import { IsNotEmpty, IsNumber, IsString } from 'class-validator';

export class CreatePostDto {
  @IsString()
  @IsNotEmpty({ message: '게시물의 제목을 입력해주세요.' })
  readonly title: string;

  @IsString()
  @IsNotEmpty({ message: '게시물의 내용을 입력해주세요.' })
  readonly content: string;

  @IsNumber()
  @IsNotEmpty({ message: '게시물의 비밀번호를 입력해주세요.' })
  readonly password: number;
}

 

// nestjs_board\src\post\dto\remove-post.dto.ts
import { PickType } from '@nestjs/mapped-types';

import { CreatePostDto } from './create-post.dto';

export class RemovePostDto extends PickType(CreatePostDto, ['password']) {} // 비번만 받음

 

// nestjs_board\src\post\dto\update-post.dto.ts
import { OmitType } from '@nestjs/mapped-types'; // 여기 타입을 수정
import { CreatePostDto } from './create-post.dto';

// OmitType : 무언가 생략한타입
 //  type를 생략해서 OmitType사용함 
export class UpdatePostDto extends OmitType(CreatePostDto, ['title']) {}

 

// post.controller.ts
import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { CreatePostDto } from './dto/create-post.dto';
import { UpdatePostDto } from './dto/update-post.dto';
import { RemovePostDto } from './dto/remove-post.dto';
import { PostService } from './post.service';

@Controller('post')
export class PostController {
  constructor(private readonly postService: PostService) {}

  @Post()
  create(@Body() createPostDto: CreatePostDto) {
    return this.postService.create(createPostDto);
  }

  @Get()
  findAll() {
    return this.postService.findAll();
  }

  // GET http://localhost:3000/post/1
  @Get(':id') // 인자가 ':'로 시작하는건 파라미터로 쓰인다는 뜻
  findOne(@Param('id') id: string) {
    return this.postService.findOne(+id);
  }

  // PATCH http://localhost:3000/post/1
  @Patch(':id')
  // @Param(파람) : 'id'를 id의 값(문자열 타입)으로 받는다는 뜻
  update(@Param('id') id: string, @Body() updatePostDto: UpdatePostDto) {
    return this.postService.update(+id, updatePostDto); // service에서는 id가 숫자타입
  }

  // DELETE http://localhost:3000/post/1
  @Delete(':id')
  remove(@Param('id') id: string, @Body() deletePostDto: RemovePostDto) {
    return this.postService.remove(+id, deletePostDto);
  }
}

 


서버 실행
npm run start # package.json 의 scripts{"start": "nest start",},

이렇게 뜨면 서버 실행중

> nestjs_board@0.0.1 start
> nest start

[Nest] 18860  - 2025. 01. 21. 오후 7:29:41     LOG [NestFactory] Starting Nest application...
[Nest] 18860  - 2025. 01. 21. 오후 7:29:41     LOG [InstanceLoader] AppModule dependencies initialized +6ms
[Nest] 18860  - 2025. 01. 21. 오후 7:29:41     LOG [InstanceLoader] PostModule dependencies initialized +0ms
[Nest] 18860  - 2025. 01. 21. 오후 7:29:41     LOG [RoutesResolver] AppController {/}: +3ms
[Nest] 18860  - 2025. 01. 21. 오후 7:29:41     LOG [RouterExplorer] Mapped {/, GET} route +1ms
[Nest] 18860  - 2025. 01. 21. 오후 7:29:41     LOG [RoutesResolver] PostController {/post}: +0ms
[Nest] 18860  - 2025. 01. 21. 오후 7:29:41     LOG [RouterExplorer] Mapped {/post, POST} route +1ms
[Nest] 18860  - 2025. 01. 21. 오후 7:29:41     LOG [RouterExplorer] Mapped {/post, GET} route +0ms
[Nest] 18860  - 2025. 01. 21. 오후 7:29:41     LOG [RouterExplorer] Mapped {/post/:id, GET} route +0ms
[Nest] 18860  - 2025. 01. 21. 오후 7:29:41     LOG [RouterExplorer] Mapped {/post/:id, PATCH} route +1ms
[Nest] 18860  - 2025. 01. 21. 오후 7:29:41     LOG [RouterExplorer] Mapped {/post/:id, DELETE} route +0ms
[Nest] 18860  - 2025. 01. 21. 오후 7:29:41     LOG [NestApplication] Nest application successfully started +1ms

_______________________________________
개시글 생성
  POST http://localhost:3000/
  
  헤더에 추가 - 컨텐트를 json타입으로 넘길거다
  Content-Type : application/json

  body에 내용 추가
  {
    "title": "열한번째 포스트",  // string
    "content": "이제 열한번째 포스트. 한참남았다", // string
    "password": 15555 // number
  }


_______________________________________
전체조회
GET http://localhost:3000/post
상세조회
GET http://localhost:3000/post/:id


_______________________________________
수정
PATCH http://localhost:3000/post
 헤더에 추가 - 컨텐트를 json타입으로 넘길거다
  Content-Type : application/json
 body에 
 {    
    "content": "이제 열한번째 포스트. 수정완료",
    "password": 15555  
 }
_______________________________________
삭제
DELETE http://localhost:3000/post:id

body에
   {"password": 15555 }

 

 

'내일배움 강의 > 강의-NestJS' 카테고리의 다른 글

6주차  (0) 2025.01.31
5주차  (0) 2025.01.27
4주차  (0) 2025.01.22
1주차  (1) 2025.01.19