저장을 습관화

NestJS - TypeORM CRUD 본문

공부/node.js

NestJS - TypeORM CRUD

ctrs 2023. 12. 29. 18:55

상황은 사용자 로그인 이후 블로그에 게시글을 작성, 조회, 수정, 삭제하는 API를 가정하며,

CRUD에 필수적인 패키지 외 가드 등은 생략한다.

 

사용하는 DB는 MySQL이다.

 

0. 사용자 정보를 가져오는 데코레이터

- user.decorator.ts

import { createParamDecorator, ExecutionContext } from '@nestjs/common';

// 세션 방식 로그인
export const User = createParamDecorator(
  (data: unknown, ctx: ExecutionContext) => {
    const request = ctx.switchToHttp().getRequest();
    return request.user;
  },
);

 

1. Create

- posting.request.dto.ts

import { PickType } from '@nestjs/swagger';
import { Posts } from '../posts.entity';

export class PostingRequestDto extends PickType(Posts, [
  'title',
  'content',
] as const) {}

엔티티 Posts에서 PickType을 이용하여 title, content 컬럼만을 가져오며 둘 모두 string 타입이다.

 

- posts.controller.ts

import {
  Body,
  Controller,
  Delete,
  Get,
  Param,
  Patch,
  Post,
  UseGuards,
} from '@nestjs/common';
import { PostsService } from './posts.service
import { PostingRequestDto } from './dto/posting.request.dto';
import { User } from '../common/decorators/user.decorator';
import { Users } from '../users/users.entity';

export class PostsController {
  constructor(private postsService: PostsService) {}

  // 글 작성
  @Post()
  async createPost(@Body() body: PostingRequestDto, @User() user: Users) {
    await this.postsService.createPost(body.title, body.content, user.id);
    return `'${body.title}' 게시글 작성 완료`;
  }
}


- posts.service.ts

import {
  ForbiddenException,
  Injectable,
  NotFoundException,
} from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Posts } from './posts.entity';
import { Repository } from 'typeorm';

@Injectable()
export class PostsService {
  constructor(
    @InjectRepository(Posts) private postsRepository: Repository<Posts>,
  ) {}
  
  // 글 작성
  async createPost(title: string, content: string, id: number) {
    await this.postsRepository.save({
      title: title,
      content: content,
      userId: id,
    });
  }
}

 

 

2. Read

- main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);

  // 모든 컨트롤러에 class-validation, class-transformer 사용  
  app.useGlobalPipes(new ValidationPipe({ transform: true })); 
  
}

URL에 들어간 내용이 파라미터로 쓰일 경우

이를 string이 아닌 number로써 사용하기 위함이다.

 

이에 대한 설명

https://ctrs.tistory.com/232

 

TypeScript - class-transformer 데이터 타입 변환

nest.js를 사용할때 유용한 패키지 데이터 타입을 변환할때 사용한다 설치 방법 $ npm install class-transformer 예를 들어, 영화의 정보 등을 작성하는 API를 만들었다고 가정하자. body에는 "title", "year", "ge

ctrs.tistory.com

 

- posts.controller.ts

  // 게시글 개수 세기 - 완료
  @Get('count')
  countPost() {
    return this.postsService.countPost();
  }

  // 전체 글 가져오기
  @Get()
  getAllPost() {
    return this.postsService.getAllPost();
  }

  // 특정 글 가져오기
  @Get(':id')
  getSpecificPost(@Param('id') postId: number) {
    return this.postsService.getSpecificPost(postId);
  }

 

- posts.service.ts

  // 게시글 개수 세기 - 완료
  async countPost() {
    return await this.postsRepository.count();
  }

  // 전체 글 가져오기 - 완료
  async getAllPost() {
    return await this.postsRepository.find({});
  }

  // 특정 글 가져오기 - 완료
  async getSpecificPost(postId: number) {
    // 게시글이 존재하는지 확인
    const post = await this.postsRepository.findOne({ where: { id: postId } });
    if (!post) {
      throw new NotFoundException('게시글이 존재하지 않습니다.');
    }
    return post;
  }

 

 

3. Update

HTTP 메소드는 PUT이 아닌 PATCH를 사용한다.

- updatePost.request.dto.ts

import { PartialType } from '@nestjs/swagger';
import { PostingRequestDto } from './posting.request.dto';

export class UpdatePostRequestDto extends PartialType(PostingRequestDto) {}

Create에 쓰였던 PostingRequestDto의 내용을 PartialType을 사용하여 상속받았다.

이를 통해 string 타입의 'title', 'content' 속성을 사용하면서, 둘 중 하나의 데이터만 서버에 전송하더라도

에러없이 처리가 가능하다. (PUT이 아닌 PATCH)

 

- posts.controller.ts

  // 글 수정
  @Patch(':id')
  async updatePost(
    @Param('id') postId: number,
    @Body() body: UpdatePostRequestDto,
    @User() user: Users,
  ) {
    await this.postsService.updatePost(
      postId,
      body.title,
      body.content,
      user.id,
    );
    return `${postId} 게시글이 수정되었습니다.`;
  }

 

- posts.service.ts

  // 글 수정
  async updatePost(
    postId: number,
    title: string,
    content: string,
    userId: number,
  ) {
    // 게시글이 존재하는지 확인
    const post = await this.postsRepository.findOne({ where: { id: postId } });
    if (!post) {
      throw new NotFoundException('게시글이 존재하지 않습니다.');
    }

    // 현재 로그인한 사용자가 글 작성자인지 확인
    if (post.userId !== userId) {
      throw new ForbiddenException('수정 권한이 없습니다.');
    }

    await this.postsRepository.update(postId, {
      title: title,
      content: content,
    });
  }

 

 

4. Delete

- posts.controller.ts

  // 글 삭제
  @Delete(':id')
  async deletePost(@Param('id') postId: number, @User() user: Users) {
    await this.postsService.deletePost(postId, user.id);
    return `${postId} 게시글이 삭제되었습니다.`;
  }

 

- posts.service.ts

  // 글 삭제
  async deletePost(id: number, userId: number) {
    // 게시글이 존재하는지 확인
    const post = await this.postsRepository.findOne({ where: { id: id } });
    if (!post) {
      throw new NotFoundException('게시글이 존재하지 않습니다.');
    }

    // 현재 로그인한 사용자가 글 작성자인지 확인
    if (post.userId !== userId) {
      throw new ForbiddenException('삭제 권한이 없습니다.');
    }

    await this.postsRepository.delete(id);
    return true;
  }