[Nest] Nest WikiDocs (4.12)

ShinJuYong·2022년 4월 12일
1

Nest

목록 보기
6/13
post-thumbnail

유저 서비스에 유효성 검사 적용하기

유저 서비스 소스에 유효성 검사할때 필요한 라이브러리를 먼저 설치한다
yarn add class-validator
yarn add class-transformer

그리고 Nest.js에서 제공하는 Pipe를 전역으로 적용한다.

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

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe({
      transform: true,
    }),
  );
  await app.listen(3001);
}
bootstrap();

유저서비스는 다음과 같은 규칙을 가진다

  • 사용자 이름은 2자 이상 30자 이하인 문자열이어야 한다.
  • 사용자 이메일은 60자 이하의 문자열로써 이메일 주소형식에 적합해야 한다.
  • 사용자 패스워드는 영문대소문자와 숫자 또는 특수문자(!, @, #, $, %, ^, &, *, (, ))로 이루어진 8자 이상 30자 이하의 문자열이어야 한다.

class-validator를 이용해 위 규칙을 적용한다.

class-transformer에서 제공하는 @Transform 데코레이터의 활용법

유저생성중 name앞뒤에 공백이 포함이 안된다고하지만 유저는 충분히 공백을 넣고 요청을 할수도있다, 이 공백을 제거하는 로직을 클라이언트에서도 수행해도되지만 백엔드에서도 추가적인 방어수단을 추가하는것이 좋은코딩

@Transform(params => params.value.trim())
@IsString()
@MinLength(2)
@MaxLength(30)
readonly name: string;

TransformFnParams에는 obj속성이 있다, obj는 현재 속성이 속해있는 객체를 가리키는데, name속성을 가지고있는 createUserDto 객체를 의미한다

예를들어, passwordname과 같은 문자열을 포함할 수 없도록 하고싶다면 다음처럼 구현이 가능하다.

커스텀 유효성 검사

Nest.js의 기술은 아니지만 class-validator로 사용이가능하며 유용한 테크닉

export function NotIn(property: string, validationOptions?: ValidationOptions) {
  // 데코레이터의 인자는 객체에서 참조하려는 다른 속성의 이름 (property)와 ValidationOptions를 받는다.
  return (object: Object, propertyName: string) => {
    // registerDecorator를 호출하는 함수를 리턴한다
    registerDecorator({
      name: 'NotIn',
      target: object.constructor,
      propertyName,
      options: validationOptions,
      constraints: [property],
      // `ValidatorConstraintInterface`이 구현된 함수
      validator: {
        validate(value: any, args: ValidationArguments) {
          const [relatedPropertyName] = args.constraints;
          const relatedValue = (args.object as any)[relatedPropertyName];
          return (
            typeof value === 'string' &&
            typeof relatedValue === 'string' &&
            !relatedValue.includes(value)
          );
        },
      },
    });
  };
}

인증과 인가의차이

  1. 인증은 유저나 디바이스의 신원을 증명하는 행위이다.
  2. 인가는 유저나 디바이스에게 접근권한을 부여하거나 거부하는 행위
  3. 인증은 인가의 한 요소가 될 수 있다.
  4. 인가 (토큰)으로 유저나 기기의 신원을 확인하는건 좋지않다.

인가를 얻기위한 수단으로 JWT를 사용하는데, 클라이언트는 발급받은 JWT를 매 요청마다 헤더에 실어보낸다
서버에서는 JWT를 유효한지 검증하고, JWT에 포함된 정보로 인가를 수행한다.

영속화를 위한 TypeORM

Nest에 MySQL을 연결하기위해 라이브러리부터 설치한다.
yarn add typeorm @nestjs/typeorm mysql2

docs에선 docker로 작업하지만 나는 pc내부의 db설치해서 사용

    TypeOrmModule.forRoot({
      type: 'mysql',
      host: 'localhost',
      port: 3306,
      username: 'root',
      password: 'qkek347',
      database: 'test',
      entities: [__dirname + '/**/*.entity{.ts,.js}'],
      synchronize: true,
      logging: true,
    }),

회원가입 저장

user.entity.ts
유저 엔티티를 정의한다

import { Column, PrimaryColumn } from 'typeorm';

export class UserEntity {
  @PrimaryColumn()
  id: string;

  @Column({ length: 30 })
  name: string;

  @Column({ length: 60 })
  email: string;

  @Column({ length: 30 })
  password: string;

  @Column({ length: 60 })
  signupVerifyToken: string;
}

USER테이블이 준비됐으니, TODO로 남겨둔 유저 함수들을 구현한다

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { EmailModule } from 'src/email/email.module';
import { UserEntity } from './entity/user.entity';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';

@Module({
  imports: [EmailModule, TypeOrmModule.forFeature([UserEntity])],
  controllers: [UsersController],
  providers: [UsersService],
})
export class UsersModule {}

UserService에 유저리포지를 주입한다

    @InjectRepository(UserEntity)
    private readonly usersRepository: Repository<UserEntity>,
private async checkUserExists(emailAddress: string): Promise<boolean> {
  const user = await this.usersRepository.findOne({ email: emailAddress });

  return user !== undefined;
}

기존 유저를 확인하는함수

    const userExist = await this.checkUserExists(email);
    if (userExist) {
      throw new UnprocessableEntityException(
        '해당 이메일로는 가입이 불가능합니다.',
      );
    }

만약 생성시에 해당 이메일로 유저가있다면 422에러를 발생시킨다.

트랜잭션

트랜잭션은 요청을 처리하는 과정에서 DB에 변경이 일어나는 요청을 독립적으로 분리하고, 에러가 발생하면 이전 상태로 되돌리기위해 DB에서 제공하는 기능

TypeORM에서는 트랜잭션을 3가지 방법으로 사용한다.

  1. QueryRunner를 이용해서 단일 DB 커넥션을 생성하고 관리
  2. transaction객체를 사용
  3. @Transaction, @TransactionManager, @TransactionRepository 데코레이터 사용

Nest.js에선 데코레이터 방식은 권장하지 않는다.

마이그레이션

우리가 생성한 DB를 기준으로 TypeORM 마이그레이션
package.json에 cli를 실행할수 있도록 명령어를 추가해준다.
"typeorm": "node --require ts-node/register ./node_modules/typeorm/cli.js"

마이그레이션 이력을 관리할 테이블을 설정해야한다.
ormconfig.json에서 관련 옵션을 추가해준다.

  "migrations": ["dist/migrations/*{.ts,.js"],
  "cli": {
    "migrationsDir": "src/migrations"
  },
  "migrationsTableName": "migrations"

migrations : 마이그레이션을 수행할 파일
cli : 마이그레이션 파일을 생성할 디렉토리
migrationsTableName : 이력이 기록되는 테이블 이름

0개의 댓글