NestJS | CRUD

Stellar·2021년 7월 7일
0

JS Runtime

목록 보기
2/13
post-thumbnail

@NestJS 시작하기

# NestJS의 전체적인 흐름

# 기본 루틴

app.module -> app.controller -> app.service

cotroller는 통신(router) 역할과 request, response를 받아서 유효성 검사등을 하고 service로 데이터를 넘겨주면 service에서 요청 받은 데이터를 가공한다. 이때 service에서 Repository를 사용하여 DB의 데이터를 가져올 수 있다.


# users 폴더 생성

nestjs 공식문서를 기준으로 작성

For quickly creating a CRUD controller with the validation built-in, you may use the CLI's CRUD generator: $ nest g resource [name].

  • --no-spec을 붙여주면 spec 파일을 제외하고 폴더가 생성된다.
    nest g resource users --no-spec

  • app.module.ts 파일에 자동으로 UsersModule이 추가된다.


# entity 작성

엔티티란 DB의 Table을 mapping 하는 클래스를 의미한다. TypeORM을 이용하여 작성한다.
(Django로 따지면 models.py에 해당됨)

# entity 컬럼 옵션

여러 옵션이 있고 참고 (Column types for mysql - TypeScript) 하여 작성할 것.

...
- name: string - DB에 보여지는 컬럼 명
- length: number - 글자 수
- unique: boolean - 유니크
...

# 관계 설정

참고. Relations - NestJS

# entity 작성

//src/users/user.entity.ts
//user 테이블
import { Column, Entity, PrimaryGeneratedColumn } from "typeorm";

@Entity()
export class User {
    @PrimaryGeneratedColumn()
    id: number;

    @Column({unique: true}) //별도의 길이 지정이 없는 경우 기본값이 255로 설정 됨.
    email: string;

    @Column({length: 50, name: 'user_name'})
    name: string;

    @Column({name: 'user_age'})
    age: number;

    @Column()
    password: string;
}

entity 작성 후 저장 또는 $ npm run start:dev으로 서버 재실행 해주면 DB에 컬럼이 생성된다.


# morgan 설정

morgan이란 express의 미들웨어로 백엔드 서버에 log(statusCode)를 남겨주는 좋은 기능이다. (아래 방법은 NestJS용)
Morgan으로 백 서버에 log(statusCode) 표시하기


# 유저 Create

# class-validator 설치

$ npm install --save class-validator class-transformer

class-validator란.
Data Validation(데이터 유효성 검사)을 하여 다루는 데이터가 올바른 Format을 가지는지 확인하는 과정 (@IsString 같은 데코레이터를 사용할 수 있다.)
사이트 참고 - typestack/class-validator - GitHub

# DTO 작성

테이블에 값 생성 및 수정하는 기능은 DTO를 생성하여 유효성 검사를 한다.
Primary Key의 경우 유효성 검사는 필요없다.

//create-user.dto.ts
import { IsEmail, IsNumber, IsString } from "class-validator";

export class CreateUserDto {
    @IsEmail()
    email: string;

    @IsString()
    name: string;

    @IsNumber()
    age: number;

    @IsString()
    password: string;
}

# class-transformer 설치

$ npm install class-transformer

class-transformer란.
plain 객체는 object 클래스의 인스턴스 객체이다. {} 표기법을 통해 생성되며, 리터럴 객체라고도 부른다.
클래스 객체는 자체 정의된 생성자, 속성, 메서드가 있는 클래스의 인스턴스이다.
리터럴 객체를 보유한 클래스의 인스턴스를 매핑할 수 있도록 해준다.
사이트 참고 - TypeScript로 개발 할 때 유용한 라이브러리 - shovelman

# main.ts파일에 useGlobalPipes를 적용

//main.ts
import { ValidationPipe } from '@nestjs/common';
...
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true,
    forbidUnknownValues: true,
    transform: true
  }))
  await app.listen(3000);
...

pipe는 아래 두 가지 기능을 담당한다.

  • 변환(transformation): 입력 데이터를 원하는 형식으로 변환(예: 문자열에서 정수로)
  • 유효성 검사(validation): 입력 데이터를 평가하고 유효한 경우 변경하지 않고 전달. 그렇지 않으면 데이터가 올바르지 않을 때 예외를 발생.
    사이트 참고 - Pipes - NestJS Doc

useGlobalPipes이란.
인터셉터를 전역 인터셉터로 등록한다. (모든 HTTP 라우트 핸들러 내에서 사용)

ValidationPipe이란.
Nest에는 기본적으로 사용할 수 있는 여러 내장된 파이프가 함께 제공되고 그 중 하나가 ValidationPipe

# TypeOrm 모듈을 추가

users 모듈에서 typeorm을 사용할 수 있도록 TypeOrm 모듈을 추가하고
db와 매핑되는 entity를 forFeature에 추가한다.

//users.module.ts
...
import { UsersService } from './users.service';
...
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';

@Module({
  imports: [TypeOrmModule.forFeature([User])],
...
  exports: [UsersService]
})
...

# Controller 설정

컨트롤러는 request, response 데이터를 전달해주는 파일이다. (장고의 urls.py와 같다)

//users.controller.ts
import { Controller, Post, Body } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
    //usersService에 작성한 create를 실행하고 createUserDto를 이용해 유효성 검사를 한다.
  }
}

# Service 작성

컨트롤러에서 넘어 온 데이터를 가공한다. (장고의 views.py와 같다)

//users.service.ts
import { ConflictException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { User } from './entities/user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>, //repository를 이용해 DB에 접근한다.
    ) {}

  async create(createUserDto: CreateUserDto): Promise<any> {
    const isExist = await this.userRepository.findOne({email: createUserDto.email});

    if (isExist) {
      throw new ConflictException({
        statusCode: HttpStatus.CONFLICT,
        message: ['ALREADY_EXIST_ACCOUNT'],
        error: 'Conflict'
      })
    }

    const { password, ...result } = await this.userRepository.save(createUserDto);
    return result;
  }

# 포스트맨 테스트

API : POST localhost:3000/user
Content-Type : application/json (프론트에서 설정)


# Bycrpt 적용하여 패스워드 암호와 하기

# Bycrpt 설치

$ npm install --save bcrypt
$ npm install --save-dev @types/bcrypt

# hash에 사용할 salt 상수값 작성

해싱할 때 조금 더 복잡하게 만들어 준다.

//src/constants.ts
export const bcryptConstant = {
    saltOrRounds: 10,
  };

# users.service hash 코드 추가

//users.service.ts
...
import * as bcrypt from 'bcrypt';
import { bcryptConstant } from 'src/constants';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
    ) {}

  async create(createUserDto: CreateUserDto): Promise<any> {
    ...
    createUserDto.password = await bcrypt.hash(createUserDto.password, bcryptConstant.saltOrRounds); // hash 코드 추가
    const { password, ...result } = await this.userRepository.save(createUserDto);
    return result;
  }

# 결과 확인


# 유저 Read, Update, Delete

# update-user.dto 작성

유저 정보 업데이트를 위한 유효성 검사 파일을 작성한다.

PartialType 사용 시 참조하는 CreateUserDto의 전체 컬럼에 대해 옵셔널이 적용되므로 컬럼을 별도로 적어 주지 않아도 된다

//update-user.dto.ts
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';

export class UpdateUserDto extends PartialType(CreateUserDto) {}

# Controller 작성

import { Controller, Get, Post, Body, Patch, Param, Delete } from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';

@Controller('users')
export class UsersController {
  constructor(private readonly usersService: UsersService) {}

  @Post()
  create(@Body() createUserDto: CreateUserDto) {
    return this.usersService.create(createUserDto);
  }

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

  @Get(':id') //파라메터 형식
  findOne(@Param('id') id: number) {
    return this.usersService.findOne(+id);
  }

  @Patch(':id')
  update(@Param('id') id: number, @Body() updateUserDto: UpdateUserDto) {
    return this.usersService.update(+id, updateUserDto);
  }

  @Delete(':id')
  remove(@Param('id') id: number) {
    return this.usersService.remove(+id);
  }
}

# Service 작성

import { ConflictException, HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';

@Injectable()
export class UsersService {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
    ) {}

  async create(createUserDto: CreateUserDto): Promise<any> {
    const isExist = await this.userRepository.findOne({email: createUserDto.email});

    if (isExist) {
      throw new ConflictException({
        statusCode: HttpStatus.CONFLICT,
        message: ['ALREADY_EXIST_ACCOUNT'],
        error: 'Conflict'
      })
    }

    const { password, ...result } = await this.userRepository.save(createUserDto);
    return result;
  }

  async findAll() {
    return this.userRepository.find();
  }

  async findOne(id: number) {
    return this.userRepository.findOne(id);
  }

  async update(id: number, updateUserDto: UpdateUserDto) {
    return this.userRepository.update(id, updateUserDto);
  }

  async remove(id: number) {
    return this.userRepository.delete(id);
  }
}

# 참고

dto.ts예시

CRUD 예시

0개의 댓글