Application - Server - Validation

9/18/2021 NestJsGraphQL

# Validation:

Để validate input có thể validate tại graphql schema hoặc dùng class-validator tại resolver.

# Graphql Schema:

Graphql Schema có 1 số scalar có thể dùng để validation cho input.

input UserCreateInput {
    name: String!
    password: String!
    email: String!
}
1
2
3
4
5

vd: Không nhập field name.

"errors": [
      {
        "message": "Field UserCreateInput.name of required type String! was not provided.",
        "locations": [
          {
            "line": 2,
            "column": 21
          }
        ],
        "extensions": {
          "code": "GRAPHQL_VALIDATION_FAILED"
        }
      }
    ]
1
2
3
4
5
6
7
8
9
10
11
12
13
14

Nhưng graphql mặc định chỉ có 1 vài scalar để validate định dạng dữ liệu để sử dụng những rule khác cần install thêm package (opens new window) dùng các directive để validate input hoặc tự viết các directive kết hợp scalar.

# Resolver:

Dùng các class dto (opens new window) để validate input.

Xử lí handle error của class-validator để đồng nhất với error của graphql.

  • Tạo custom ValidationPipe:

    Nếu có lỗi thì throw ValidationError của apollo-server-express

    import { Injectable, PipeTransform, ArgumentMetadata, Type } from '@nestjs/common';
    import { validate } from 'class-validator';
    import { plainToClass } from 'class-transformer';
    import { ValidationError } from 'apollo-server-express';
    
    @Injectable()
    export class ValidationPipe implements PipeTransform {
      async transform(value: any, {metatype}: ArgumentMetadata) {
        if (!metatype || !this.toValidate(metatype)) {
          return value;
        }
        const object = plainToClass(metatype, value);
        const errors = await validate(object);
        if (errors.length > 0) {
          throw new ValidationError(
              `${this.formatErrors(errors)}`,
          );
        }
        return value;
      }
    
      private toValidate(metatype: Type<any> | undefined): boolean {
        const types: any[] = [String, Boolean, Number, Array, Object];
        return !types.includes(metatype);
      }
    
      private formatErrors(errors: any[]) {
        return errors
            .map(err => {
              for (const property in err.constraints) {
                return err.constraints[property];
              }
            })
            .join(', ');
      }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
  • Binding ValidationPipe tại main.ts

    app.useGlobalPipes(new ValidationPipe());
    
    1

# Tạo custom validation class:

Tạo 1 custom validation class để validate unique cho field email. Code tham khảo tại

https://gist.github.com/zarv1k/3ce359af1a3b2a7f1d99b4f66a17f1bc (opens new window)

Xử lí validate: dùng MongoRepository count dữ liệu theo điều kiện.

public async validate<E>(value: string, args: UniqueValidationArguments<E>) {
    const [EntityClass, findCondition = args.property] = args.constraints;
    const cond = typeof findCondition === 'function'
        ? findCondition(args)
        : {
          [findCondition || args.property]: value,
        };

    return (
        (await this.connection.getRepository(EntityClass).count(cond)) <= 0
    );
  }
1
2
3
4
5
6
7
8
9
10
11
12

Theo hướng dẫn trên, khi dùng thì sẽ phát sinh lỗi connection underfined, vì không inject được connection.

@ValidatorConstraint({ name: 'unique', async: true })
@Injectable()
export class Unique extends UniqueValidator {
  constructor(@InjectConnection() protected readonly connection: Connection) {
    super(connection);
  }
}
1
2
3
4
5
6
7

Cần thêm set DI-container của class-validator tại main.ts

useContainer(app.select(AppModule), { fallbackOnErrors: true });
1

Sử dụng tại Dto:

export class UserCreateInputDto extends UserCreateInput {
	...

    @IsEmail()
    @Validate(Unique, [User, 'email'])
    email: string;
}
1
2
3
4
5
6
7

Series:

GitHub: https://github.com/ninhnguyen22/nestjs-angular-graphql (opens new window)