๐ŸŽ‰ berenickt ๋ธ”๋กœ๊ทธ์— ์˜จ ๊ฑธ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค. ๐ŸŽ‰
Back
NestJs
18-Class Validator

1. Class Validator์™€ DTO ์‚ฌ์šฉ

1.1 Class Validator ํŠน์„ฑ

  • TS Decorator๋ฅผ ์‚ฌ์šฉํ•ด์„œ ํด๋ž˜์Šค๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค (Validate)
  • ๋™๊ธฐ (Synchronous), ๋น„๋™๊ธฐ (Asynchronous) ๋ฐฉ์‹ ๋ชจ๋‘๋ฅผ ์ง€์›ํ•œ๋‹ค.
  • Class Validator ์ž์ฒด์ ์œผ๋กœ ์ œ๊ณตํ•ด์ฃผ๋Š” Validator๋“ค์„ ์‚ฌ์šฉ ํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ปค์Šคํ…€ Validator๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.
  • ์ปค์Šคํ…€ ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋ฅผ ๋ฐ˜ํ™˜ ํ•  ์ˆ˜ ์žˆ๋‹ค.

1.2 Class Validator ์„ค์น˜ ๋ฐ ์‚ฌ์šฉ๋ฒ•

(1) Class Validator ์„ค์น˜

1
yarn add class-validator

(2) Class Validator ์‚ฌ์šฉ๋ฒ•

1
class User {
2
@IsNotEmpty()
3
name: string
4
5
@IsEmail()
6
email: string
7
}

์–ด๋–ค Validator๋“  ๊ฒ€์ฆํ•˜๊ณ ์‹ถ์€ ํ”„๋กœํผํ‹ฐ์— Class Validator์—์„œ ์ œ๊ณตํ•˜๋Š” Decorator๋กœ ๋ถ™์—ฌ์ฃผ๋ฉด ๋œ๋‹ค.

1
const user = new User()
2
3
user.name = ''
4
user.email = 'invalid-email'
5
6
validate(user).then(errors => {
7
// ์—ฌ๊ธฐ์„œ ์—๋Ÿฌ ๋ฐ˜ํ™˜
8
})

validate()๋กœ ๊ฐ์ฒด๋ฅผ ๊ฒ€์ฆํ–ˆ์„๋•Œ, Class Validator์— ๋ถ€ํ•ฉํ•˜์ง€ ์•Š์€ ๊ฐ’์ด ์ž…๋ ฅ๋˜๋ฉด, ํ•ด๋‹น๋˜๋Š” ์—๋Ÿฌ๊ฐ€ ๋ฐ˜ํ™˜๋œ๋‹ค.


1.3 DTO (Data Transfer Object)

class-validator๋กœ Body์˜ ํ”„๋กœํผํ‹ฐ ๊ฐ’๋“ค์„ ํ•˜๋‚˜์˜ ํด๋ž˜์Šค๋กœ ๋ฌถ์–ด์„œ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ๊ทธ๋ฆฌ๊ณ  ์ด๋ ‡๊ฒŒ ๋ฌถ์€ ํ˜•ํƒœ์˜ ํด๋ž˜์Šค๋ฅผ DTO๋ผ๊ณ  ๋ถ€๋ฅธ๋‹ค.
  • DTO(Data Transfer Object; ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†กํ•˜๋Š” ๊ฐ์ฒด)
    • ํด๋ผ์ด์–ธํŠธ๋กœ๋ถ€ํ„ฐ ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ๋ฅผ ์ „์†ก๋ฐ›์œผ๋ฉด,
    • ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„์—์„œ ํšจ์œจ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๊ด€๋ฆฌํ•ด์ฃผ๋Š” ๊ฐ์ฒด๋ผ๋Š” ๋œป

posts/dto/create-post.dto.ts ํŒŒ์ผ์„ ๋งŒ๋“ ๋‹ค.

create-post.dto.ts
1
import { IsString } from 'class-validator'
2
3
export class CreatePostDto {
4
@IsString()
5
title: string
6
7
@IsString()
8
content: string
9
}
  • cf. DTO๋Š” API๋ž‘ 1:1 ๋งตํ•‘์ด ์•„๋‹Œ ๊ฒฝ์šฐ๋„ ์žˆ๋‹ค.
  • ๊ทธ๋ž˜์„œ CreatePostDTO๋ฅผ ๋‹ค๋ฅธ API์—์„œ๋„ ์“ฐ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๊ธฐ์— ์กฐ๊ธˆ ๋” ์ผ๋ฐ˜ํ™”๋œ ์ด๋ฆ„์œผ๋กœ ์ง“๋Š”๋‹ค.

posts ์ปจํŠธ๋กค๋Ÿฌ์— DTO๋ฅผ ์ ์šฉ์‹œํ‚จ๋‹ค.

posts.controller.ts
1
// posts.controller.ts ์ƒ๋žต
2
@Post()
3
@UseGuards(AccessTokenGuard)
4
postPosts(
5
@User('id') userId: number,
6
@Body() body: CreatePostDto,
7
// @Body('title') title: string,
8
// @Body('content') content: string,
9
// ๊ธฐ๋ณธ๊ฐ’์„ true๋กœ ์„ค์ •ํ•˜๋Š” ํŒŒ์ดํ”„
10
// @Body('isPublic', new DefaultValuePipe(true)) isPublic: boolean,
11
) {
12
return this.postsService.createPost(userId, body)
13
}

posts ์„œ๋น„์Šค์—๋„ DTO๋ฅผ ์ ์šฉ์‹œ์ผœ์ค€๋‹ค.

posts.service.ts
1
// posts.service.ts ์ƒ๋žต
2
/**
3
* 1) create : ์ €์žฅํ•  ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑ
4
* 2) save : ๊ฐ์ฒด๋ฅผ ์ €์žฅ (create ๋ฉ”์„œ๋“œ์—์„œ ์ƒ์„ฑํ•œ ๊ฐ์ฒด๋กœ)
5
*/
6
async createPost(authorId: number, postDto: CreatePostDto) {
7
const post = this.postsRepository.create({
8
author: {
9
id: authorId,
10
},
11
...postDto,
12
likeCount: 0,
13
commentCount: 0,
14
})
15
const newPost = await this.postsRepository.save(post)
16
return newPost
17
}

main.ts์—์„œ ์•ฑ ์ „์ฒด์— ํŒŒ์ดํ”„๋ฅผ ์ „์—ญ์ ์œผ๋กœ ์„ค์ •ํ•œ๋‹ค.

main.ts
1
import { NestFactory } from '@nestjs/core'
2
import { AppModule } from './app.module'
3
import { ValidationPipe } from '@nestjs/common'
4
5
async function bootstrap() {
6
const app = await NestFactory.create(AppModule)
7
app.useGlobalPipes(new ValidationPipe()) // ์ถ”๊ฐ€
8
await app.listen(3000)
9
}
10
bootstrap()
  • ํ•ด๋‹น ์•ฑ์— ์ „๋ฐ˜์ ์œผ๋กœ ์ ์šฉํ•œ ํŒŒ์ดํ”„๋ฅผ ๋„ฃ์–ด์ฃผ๋Š” ๊ฒƒ์ธ๋ฐ,
  • ValidationPipe๋ฅผ ๋„ฃ์–ด์ฃผ๋ฉด ๋ชจ๋“  class-validator๋“ค์˜ ์• ๋…ธํ…Œ์ด์…˜๋“ค์ด
  • ๋”ฐ๋กœ ์ปจํŠธ๋กค๋Ÿฌ์—๋‹ค๊ฐ€ validation ์ ์šฉํ•˜๊ฑฐ๋‚˜, ๋ชจ๋“ˆ์—๋‹ค validation ๋ชจ๋“ˆ์„ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์•„๋„
  • ์•ฑ ์ „๋ฐ˜์ ์œผ๋กœ validation์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.

2. ๊ธฐ๋ณธ ์ œ๊ณต Class Validator

๋ฐ์ฝ”๋ ˆ์ดํ„ฐ์„ค๋ช…
๊ณตํ†ต Validator
@IsDefined(value: any)๊ฐ’์ด ์ •์˜๋˜์—ˆ๋Š”์ง€ ํ™•์ธ(!== undefined, !== null).
์ด ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ๋Š” skipMissingProperties ์˜ต์…˜์„ ๋ฌด์‹œํ•˜๋Š” ์œ ์ผํ•œ ๋ฐ์ฝ”๋ ˆ์ดํ„ฐ
@IsOptional์ฃผ์–ด์ง„ ๊ฐ’์ด ๋น„์–ด ์žˆ๋Š”์ง€(=== null, === undefined ์•Š์Œ) ํ™•์ธ,
๊ทธ๋ ‡๋‹ค๋ฉด ํ”„๋กœํผํ‹ฐ์˜ ๋ชจ๋“  ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๊ธฐ๋ฅผ ๋ฌด์‹œ
@Equals๊ฐ’์ด ๊ฐ™์Œ(โ€===โ€) ๋น„๊ต์ธ์ง€ ํ™•์ธ
@NotEquals๊ฐ’์ด ๊ฐ™์ง€ ์•Š์€(โ€!==โ€) ๋น„๊ต์ธ์ง€ ํ™•์ธ
@IsEmpty์ฃผ์–ด์ง„ ๊ฐ’์ด ๋น„์–ด ์žˆ๋Š”์ง€(=== โ€™โ€™, === null, === undefined) ํ™•์ธ
@IsNotEmpty์ฃผ์–ด์ง„ ๊ฐ’์ด ๋น„์–ด ์žˆ์ง€ ์•Š์€์ง€(!== โ€, !== null, !== undefined) ํ™•์ธ
@IsIn(values: any[])๊ฐ’์ด ํ—ˆ์šฉ๋œ ๊ฐ’์˜ ๋ฐฐ์—ด์— ์žˆ๋Š”์ง€ ํ™•์ธ
@IsNotIn(values: any[])ํ—ˆ์šฉ๋˜์ง€ ์•Š๋Š” ๊ฐ’์˜ ๋ฐฐ์—ด์— ๊ฐ’์ด ์—†๋Š”์ง€ ํ™•์ธ
ํƒ€์ž… Validator
@Isboolean๊ฐ’์ด boolean์ธ์ง€ ํ™•์ธ
@IsDate๊ฐ’์ด ๋‚ ์งœ์ธ์ง€ ํ™•์ธ
@IsString๊ฐ’์ด ๋ฌธ์ž์—ด์ธ์ง€ ํ™•์ธ
@IsNumber(options: IsNumberOptions)๊ฐ’์ด ์ˆซ์ž์ธ์ง€ ํ™•์ธ
@isInt๊ฐ’์ด ์ •์ˆ˜์ธ์ง€ ํ™•์ธ
@IsArray๊ฐ’์ด ๋ฐฐ์—ด์ธ์ง€ ํ™•์ธ
@IsEnum(entity: object)๊ฐ’์ด ์œ ํšจํ•œ ์—ด๊ฑฐํ˜•์ธ์ง€ ํ™•์ธ
์ˆซ์ž Validator
@IsDivisibleBy๊ฐ’์ด ๋‹ค๋ฅธ ๊ฐ’์œผ๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋Š” ์ˆซ์ž์ธ์ง€ ํ™•์ธ
@isPositive๊ฐ’์ด 0๋ณด๋‹ค ํฐ ์–‘์ˆ˜์ธ์ง€ ํ™•์ธ
@IsNegative๊ฐ’์ด 0๋ณด๋‹ค ์ž‘์€ ์Œ์ˆ˜์ธ์ง€ ํ™•์ธ
@Min์ฃผ์–ด์ง„ ์ˆซ์ž๊ฐ€ ์ฃผ์–ด์ง„ ์ˆซ์ž๋ณด๋‹ค ํฌ๊ฑฐ๋‚˜ ๊ฐ™์€์ง€ ํ™•์ธ
@Max์ฃผ์–ด์ง„ ์ˆซ์ž๊ฐ€ ์ฃผ์–ด์ง„ ์ˆซ์ž๋ณด๋‹ค ์ž‘๊ฑฐ๋‚˜ ๊ฐ™์€์ง€ ํ™•์ธ
๋ฌธ์ž Validator
@Contains(seed: string)๋ฌธ์ž์—ด์— seed๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธ
@NotContains(seed: string)๋ฌธ์ž์—ด์— seed๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š์€์ง€ ํ™•์ธ
@IsAlphanumeric๋ฌธ์ž์—ด์— ๋ฌธ์ž์™€ ์ˆซ์ž๋งŒ ํฌํ•จ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธ
@IsCreditCard๋ฌธ์ž์—ด์ด ์‹ ์šฉ ์นด๋“œ์ธ์ง€ ํ™•์ธ
@IsHexColor๋ฌธ์ž์—ด์ด 16์ง„์ˆ˜ ์ƒ‰์ƒ์ธ์ง€ ํ™•์ธ
@MaxLength๋ฌธ์ž์—ด์˜ ๊ธธ์ด๊ฐ€ ์ฃผ์–ด์ง„ ์ˆซ์ž๋ณด๋‹ค ๊ธธ์ง€ ์•Š์€์ง€ ํ™•์ธ
@MinLength๋ฌธ์ž์—ด์˜ ๊ธธ์ด๊ฐ€ ์ฃผ์–ด์ง„ ์ˆซ์ž๋ณด๋‹ค ์ž‘์ง€ ์•Š์€์ง€ ํ™•์ธ
@IsUUID๋ฌธ์ž์—ด์ด UUID(๋ฒ„์ „ 3, 4, 5 ๋˜๋Š” ๋ชจ๋‘ )์ธ์ง€ ํ™•์ธ
@IsLatLong๊ฐ’์˜ ํ˜•์‹์ด ์œ„๋„, ๊ฒฝ๋„ ์ธ์ง€ ํ™•์ธ

3. ๋ฐ˜ํ™˜ ์—๋Ÿฌ ๊ตฌ์กฐ

1
{
2
target: Object;
3
property: string;
4
value: any;
5
constraints?: {
6
[type: string]: string;
7
};
8
children?: ValidationError[];
9
}
  • target: ๊ฒ€์ฆํ•œ ๊ฐ์ฒด
  • property: ๊ฒ€์ฆ ์‹คํŒจํ•œ ํ”„๋กœํผํ‹ฐ
  • value: ๊ฒ€์ฆ ์‹คํŒจํ•œ ๊ฐ’
  • constraints: ๊ฒ€์ฆ ์‹คํŒจํ•œ ์ œ์•ฝ์กฐ๊ฑด
  • children: ํ”„๋กœํผํ‹ฐ์˜ ๋ชจ๋“  ๊ฒ€์ฆ ์‹คํŒจ ์ œ์•ฝ ์กฐ๊ฑด

3.1 Class Validator ์—๋Ÿฌ๋ฉ”์‹œ์ง€ ๋ณ€๊ฒฝ

create-post.dto.ts
1
import { IsString } from 'class-validator'
2
3
export class CreatePostDto {
4
@IsString({
5
message: 'title์€ string ํƒ€์ž…์„ ์ž…๋ ฅํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.',
6
})
7
title: string
8
9
@IsString({
10
message: 'content์€ string ํƒ€์ž…์„ ์ž…๋ ฅํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.',
11
})
12
content: string
13
}

Decorator์˜ message ํ”„๋กœํผํ‹ฐ์— ๊ฒ€์ฆ ์‹คํŒจํ–ˆ์„๋•Œ์˜ ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋ฅผ ์ž…๋ ฅํ•ด์ฃผ๋ฉด ๋œ๋‹ค


4. PickType ํ™œ์šฉ

posts ์—”ํ‹ฐํ‹ฐ๋ฅผ ํ™œ์šฉํ•ด ์ค‘๋ณต๋œ ์ฝ”๋“œ๋ฅผ ๋” ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค.

posts.entity.ts
1
// ์ƒ๋žต
2
3
@Entity()
4
export class PostsModel extends BaseModel {
5
// ์ƒ๋žต
6
@Column()
7
@IsString({
8
message: 'title์€ string ํƒ€์ž…์„ ์ž…๋ ฅํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.',
9
})
10
title: string
11
12
@Column()
13
@IsString({
14
message: 'content์€ string ํƒ€์ž…์„ ์ž…๋ ฅํ•ด์ค˜์•ผ ํ•ฉ๋‹ˆ๋‹ค.',
15
})
16
content: string
17
// ์ƒ๋žต
18
}

create-post DTO๋Š” Pick ํƒ€์ž…์„ ํ™œ์šฉํ•˜๋Š”๋ฐ, TS์˜ ํƒ€์ž…์ด ์•„๋‹Œ ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๋Š”๋ฐ ๋˜‘๊ฐ™์€ ๊ธฐ๋Šฅ์„ ํ•˜๋Š” ํด๋ž˜์Šค๋ฅผ NestJS์—์„œ ์ œ๊ณตํ•ด์ค€๋‹ค.

create-post.dto.ts
1
import { PickType } from '@nestjs/mapped-types'
2
import { PostsModel } from '../entities/posts.entity'
3
4
/***
5
* Pick, Omit, Partial -> Type์„ ๋ฐ˜ํ™˜
6
* PickType, OmitType, PartialType -> ๊ฐ’์„ ๋ฐ˜ํ™˜
7
*/
8
export class CreatePostDto extends PickType(PostsModel, ['title', 'content']) {}

5. IsOptional Annotation ์‚ฌ์šฉ

posts ์—…๋ฐ์ดํŠธ๋Š” ์ˆ˜์ •๋ฐ›๋Š” ๊ฐ’์„ ์˜ต์…˜์œผ๋กœ ๋ฐ›๊ณ  ์žˆ๋‹ค. ์—ฌ๊ธฐ์—๋„ DTO๋ฅผ ์ ์šฉ์‹œํ‚ค์ž.

update-post.dto.ts
1
import { IsOptional, IsString } from 'class-validator'
2
import { CreatePostDto } from './create-post.dto'
3
import { PartialType } from '@nestjs/mapped-types'
4
5
/***
6
* Pick, Omit, Partial -> Type์„ ๋ฐ˜ํ™˜
7
* PickType, OmitType, PartialType -> ๊ฐ’์„ ๋ฐ˜ํ™˜
8
*/
9
export class UpdatePostDto extends PartialType(CreatePostDto) {
10
@IsString()
11
@IsOptional()
12
title?: string
13
14
@IsString()
15
@IsOptional()
16
content?: string
17
}

์ƒ์„ฑํ•œ updateDTO๋ฅผ psots ์ปจํŠธ๋กค๋Ÿฌ์— ์ ์šฉํ•œ๋‹ค.

posts.controller.ts
1
@Patch(':id')
2
putPost(
3
@Param('id', ParseIntPipe) id: number, //
4
@Body() body: UpdatePostDto,
5
// @Body('title') title?: string,
6
// @Body('content') content?: string,
7
) {
8
return this.postsService.updatePost(id, body)
9
}

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ posts ์„œ๋น„์Šค์—๋„ DTO๋ฅผ ์ ์šฉ์‹œํ‚จ๋‹ค.

posts.service.ts
1
// posts.service.ts ์ƒ๋žต
2
async updatePost(postId: number, postDto: UpdatePostDto) {
3
const { title, content } = postDto // ์ถ”๊ฐ€
4
const post = await this.postsRepository.findOne({
5
where: { id: postId },
6
})
7
8
if (!post) throw new NotFoundException()
9
if (title) post.title = title
10
if (content) post.content = content
11
12
const newPost = await this.postsRepository.save(post)
13
return newPost
14
}

6. Put ์š”์ฒญ Patch๋กœ ๋ณ€๊ฒฝ

posts.controller.ts
1
// posts.controller.ts
2
/*** 4) PATCH /posts/:id
3
* id์— ํ•ด๋‹นํ•˜๋Š” post๋ฅผ ๋ถ€๋ถ„ ๋ณ€๊ฒฝํ•œ๋‹ค
4
*/
5
@Patch(':id')
6
patchPost(
7
@Param('id', ParseIntPipe) id: number, //
8
@Body() body: UpdatePostDto,
9
) {
10
return this.postsService.updatePost(id, body)
11
}
  • Put : ์—…๋ฐ์ดํŠธํ•  ๊ฐ’๋“ค์„ ์ „๋ถ€ ์ž…๋ ฅํ•ด์ค˜์•ผ ํ•จ
    • ๊ทธ ๊ฐ์ฒด๊ฐ€ ์กด์žฌํ•˜๋ฉด ์—…๋ฐ์ดํŠธํ•˜๊ณ ,
    • ๋งŒ์•ฝ ์กด์žฌํ•˜์ง€ ์•Š๋‹ค๋ฉด, ์ƒˆ๋กœ ์ƒ์„ฑํ•œ๋‹ค.
  • Patch : ๋ถ€๋ถ„์ ์œผ๋กœ๋งŒ ์ž…๋ ฅ๋ฐ›๊ณ  ์ž…๋ ฅ๋œ ๋ถ€๋ถ„๋งŒ ์—…๋ฐ์ดํŠธํ•œ๋‹ค.
    • ํฌ์ŠคํŠธ๋งจ์—์„œ๋„ put์„ patch๋กœ ๊ณ ์นœ๋‹ค.

7. Length Annotation๊ณผ Email Annotation ์‚ฌ์šฉ

auth/dto/register-user.dto.ts ํŒŒ์ผ์„ ๋งŒ๋“ ๋‹ค.

register-user.dto.ts
1
import { PickType } from '@nestjs/mapped-types'
2
import { UsersModel } from 'src/users/entities/users.entity'
3
4
export class RegisterUserDto extends PickType(UsersModel, ['nickname', 'email', 'password']) {}

user ์—”ํ‹ฐํ‹ฐ๋ฅผ dto๊ฐ€ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ class-validator, ๊ธฐํƒ€ ์–ด๋…ธํ…Œ์ด์…˜ ๋“ฑ์„ ์ถ”๊ฐ€ํ•ด ์ˆ˜์ •ํ•œ๋‹ค.

users.entity.ts
1
// ์ƒ๋žต
2
3
@Entity()
4
export class UsersModel extends BaseModel {
5
/*** ๋‹‰๋„ค์ž„ ํŠน์„ฑ
6
* 1) ๊ธธ์ด๊ฐ€ 20์„ ๋„˜์ง€ ์•Š์„ ๊ฒƒ
7
* 2) ์œ ์ผ๋ฌด์ดํ•œ ๊ฐ’์ด ๋  ๊ฒƒ
8
*/
9
@Column({
10
length: 20,
11
unique: true,
12
})
13
@IsString()
14
@Length(1, 20, {
15
message: '๋‹‰๋„ค์ž„์€ 1~20์ž ์‚ฌ์ด๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.',
16
})
17
nickname: string
18
19
/*** ์ด๋ฉ”์ผ ํŠน์„ฑ
20
* 1) ์œ ์ผ๋ฌด์ดํ•œ ๊ฐ’์ด ๋  ๊ฒƒ
21
*/
22
@Column({
23
unique: true,
24
})
25
@IsString()
26
@IsEmail()
27
email: string
28
29
@Column()
30
@IsString()
31
@Length(3, 8)
32
password: string
33
34
@Column({
35
// role ํŠน์„ฑ์˜ ํƒ€์ž…์„ RolesEnum์˜ ๋ชจ๋“  ๊ฐ’๋“ค๋กœ ์ง€์ •
36
enum: Object.values(RolesEnum),
37
default: RolesEnum.USER,
38
})
39
role: RolesEnum
40
41
@OneToMany(() => PostsModel, post => post.author)
42
posts: PostsModel[]
43
}

๊ทธ๋ฆฌ๊ณ  ์ƒ์„ฑํ•œ DTO๋ฅผ auth ์ปจํŠธ๋กค๋Ÿฌ์— ์ ์šฉํ•œ๋‹ค.

auth.controller.ts
1
// auth.controller.ts ์ƒ๋žต
2
@Post('register/email')
3
postRegisterEmail(@Body() body: RegisterUserDto) {
4
return this.authService.registerWithEmail(body)
5
}

๋งˆ์ฐฌ๊ฐ€์ง€๋กœ ์ƒ์„ฑํ•œ DTO๋ฅผ auth ์„œ๋น„์Šค์— ์ ์šฉํ•œ๋‹ค.

auth.service.ts
1
// auth.service.ts ์ƒ๋žต
2
async registerWithEmail(user: RegisterUserDto) {
3
const hash = await bcrypt.hash(user.password, HASH_ROUNDS)
4
const newUser = await this.usersService.createUser({
5
...user, //
6
password: hash,
7
})
8
return this.loginUser(newUser)
9
}

ํฌ์ŠคํŠธ๋งจ์—์„œ ํ™•์ธํ•œ๋‹ค.


8. Validation Message ์ผ๋ฐ˜ํ™”

๊ณตํ†ต ๋ฉ”์‹œ์ง€๋ฅผ ์ž‘์„ฑํ•  common/validation-message/length-validation.message.ts ํŒŒ์ผ์„ ๋งŒ๋“ ๋‹ค.

common/validation-message/length-validation.message.ts
1
import { ValidationArguments } from 'class-validator'
2
3
/** ValidationArguments์˜ ํ”„๋กœํผํ‹ฐ๋“ค
4
* 1) value -> ๊ฒ€์ฆ๋˜๊ณ  ์žˆ๋Š” ๊ฐ’(์ž…๋ ฅ๋œ ๊ฐ’)
5
* 2) constraints -> ํŒŒ๋ผ๋ฏธํ„ฐ์— ์ž…๋ ฅ๋œ ์ œํ•œ์‚ฌํ•ญ๋“ค
6
* args.constraints[0] -> 1
7
* args.constraints[1] -> 20
8
* 3) targetNmae -> ๊ฒ€์ฆํ•˜๊ณ  ์žˆ๋Š” ํด๋ž˜์Šค์˜ ์ด๋ฆ„
9
* 4) object -> ๊ฒ€์ฆํ•˜๊ณ  ์žˆ๋Š” ๊ฐ์ฒด
10
* 5) property -> ๊ฒ€์ฆ๋˜๊ณ  ์žˆ๋Š” ๊ฐ์ฒด์˜ ํ”„๋กœํผํ‹ฐ ์ด๋ฆ„
11
* @see https://github.com/typestack/class-validator?tab=readme-ov-file#validation-messages
12
*/
13
export const lengthValidationMessage = (args: ValidationArguments) => {
14
if (args.constraints.length === 2) {
15
return `${args.property}์€ ${args.constraints[0]}~${args.constraints[1]} ๊ธ€์ž๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!`
16
} else {
17
return `${args.property}์€ ์ตœ์†Œ ${args.constraints[0]} ๊ธ€์ž๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!`
18
}
19
}

common/validation-message/string-validation.message.ts ํŒŒ์ผ์„ ๋งŒ๋“ ๋‹ค.

common/validation-message/string-validation.message.ts
1
import { ValidationArguments } from 'class-validator'
2
3
export const stringValidationMessage = (args: ValidationArguments) => {
4
return `${args.property}์— string์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!`
5
}

common/validation-message/email-validation.message.ts ํŒŒ์ผ์„ ๋งŒ๋“ ๋‹ค.

email-validation.message.ts
1
import { ValidationArguments } from 'class-validator'
2
3
export const emailValidationMessage = (args: ValidationArguments) => {
4
return `${args.property}์— ์ •ํ™•ํ•œ ์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!`
5
}

user ์—”ํ‹ฐํ‹ฐ์— ์ž‘์„ฑํ•œ Validation ๊ณตํ†ต ๋ฉ”์‹œ์ง€๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

users.entity.ts
1
// ์ƒ๋žต
2
3
@Entity()
4
export class UsersModel extends BaseModel {
5
/*** ๋‹‰๋„ค์ž„ ํŠน์„ฑ
6
* 1) ๊ธธ์ด๊ฐ€ 20์„ ๋„˜์ง€ ์•Š์„ ๊ฒƒ
7
* 2) ์œ ์ผ๋ฌด์ดํ•œ ๊ฐ’์ด ๋  ๊ฒƒ
8
*/
9
@Column({
10
length: 20,
11
unique: true,
12
})
13
@IsString({
14
message: stringValidationMessage,
15
})
16
@Length(1, 20, {
17
message: lengthValidationMessage,
18
})
19
nickname: string
20
21
/*** ์ด๋ฉ”์ผ ํŠน์„ฑ
22
* 1) ์œ ์ผ๋ฌด์ดํ•œ ๊ฐ’์ด ๋  ๊ฒƒ
23
*/
24
@Column({
25
unique: true,
26
})
27
@IsString({
28
message: stringValidationMessage,
29
})
30
@IsEmail(
31
{},
32
{
33
message: emailValidationMessage,
34
},
35
)
36
email: string
37
38
@Column()
39
@IsString({
40
message: stringValidationMessage,
41
})
42
@Length(3, 8, {
43
message: lengthValidationMessage,
44
})
45
password: string
46
47
// ์ƒ๋žต
48
}

posts ์—”ํ‹ฐํ‹ฐ, update-post.dto์—๋„ ๊ฐ๊ฐ ์ž‘์„ฑํ•œ Validation ๊ณตํ†ต ๋ฉ”์‹œ์ง€๋ฅผ ์ถ”๊ฐ€ํ•œ๋‹ค.

posts.entity.ts
1
// posts.entity.ts ์ƒ๋žต
2
@Column()
3
@IsString({
4
message: stringValidationMessage,
5
})
6
title: string
7
8
@Column()
9
@IsString({
10
message: stringValidationMessage,
11
})
12
content: string
update-post.dto.ts
1
// update-post.dto.ts ์ƒ๋žต
2
export class UpdatePostDto extends PartialType(CreatePostDto) {
3
@IsString({
4
message: stringValidationMessage,
5
})
6
@IsOptional()
7
title?: string
8
9
@IsString({
10
message: stringValidationMessage,
11
})
12
@IsOptional()
13
content?: string
14
}