유저 서비스 소스에 유효성 검사할때 필요한 라이브러리를 먼저 설치한다
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 객체를 의미한다
예를들어,
password
는name
과 같은 문자열을 포함할 수 없도록 하고싶다면 다음처럼 구현이 가능하다.
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)
);
},
},
});
};
}
인가를 얻기위한 수단으로
JWT
를 사용하는데, 클라이언트는 발급받은 JWT를 매 요청마다 헤더에 실어보낸다
서버에서는JWT
를 유효한지 검증하고,JWT
에 포함된 정보로 인가를 수행한다.
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가지 방법으로 사용한다.
QueryRunner
를 이용해서 단일 DB 커넥션을 생성하고 관리transaction
객체를 사용@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
: 이력이 기록되는 테이블 이름