Skip to content

B. Custom Decorator Validation

Pada materi sebelumnya kita sudah berhasil untuk berhasil menambahkna data, namun ketika kita menambahkan data dengan email yang sama pada tabel konsumen, maka akan muncul pesan Ada Kesalahan seperti pada gambar di bawah.

Alt text

Kalau tersebut dikarenkan email bersifat unique , sehingga tidak menerika email yang sama pada field email. Maka terlihat pada log di terminal muncul pesan seperti berikut.

Alt text

Untuk mengtasi hal tersebut , kita kita gunakan 2 pendekatan,

Pertama

kita buat pengecekan ke data ke tabel di service. Hal ini pernah kita lakukan pada kode register di auth.service.ts

auth.service.ts
async register(payload: RegisterDto): Promise<ResponseSuccess> {
    const checkUserExists = await this.authRepository.findOne({
      where: {
        email: payload.email,
      },
    });
    if (checkUserExists) {
      throw new HttpException("User already registered", HttpStatus.FOUND);
    }

    payload.password = await hash(payload.password, 12); //hash password
    await this.authRepository.save(payload);

    return this._success("Register Berhasil");
  }

Namun kalau kita mengguanakan pendekatan ini , maka jika ada field yang unique kita harus buat koding pengecekan di setiap service pada fitur.

Kedua

Pada pendekatan kedua ini kita akan membuat Custom Decorator yang akan kita implementasikan di Entity seperti koding berikut.

konsumen.dto.ts
export class KonsumenDto {
  @IsInt()
  id: number;

  @IsString()
  @IsNotEmpty()
  nama_konsumen: string;

  @IsString()
  @IsNotEmpty()
  alamat_konsumen: string;

  @IsString()
  @IsEmail()
  @IsExists([Konsumen, 'email'])
  email: string;

  @IsString()
  @IsNotEmpty()
  @MaxLength(13)
  @MinLength(9)
  nomor_handphone: string;

  @IsObject()
  @IsOptional()
  updated_by: { id: number };
  @IsObject()
  @IsOptional()
  created_by: { id: number };
}

Dengan pendekatan ini, kita cukup membuat satu Custom Validator yang bisa kita gunakan pada setiap entity.

1.Membuat Custom Decorator

a. ubah main.ts

Untuk membut Custom Validator langkah pertama kita tambahkan app.useGlobalPipes(new ValidationPipe({ transform: true })); pada main.ts .

main.ts
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ValidationPipe } from '@nestjs/common';
import { useContainer } from 'class-validator';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(
    new ValidationPipe({
      whitelist: true,
      forbidUnknownValues: true,
      transform: true,
      validateCustomDecorators: true,
      transformOptions: {
        enableImplicitConversion: true,
      },
    }),
  );
  useContainer(app.select(AppModule), { fallbackOnErrors: true });
  await app.listen(5002);
}
bootstrap();

b. Buat File unique.validator.ts

kita akan membuat file unique.validator.ts pada folder utils/validator/unique.validator.ts

Alt text

c.ValidatorConstraint

Selanjutnya kita akan membaut ValidatorConstraint dan juga class yang berisi logic untuk mengecek suatu data , apakah sudah ada di tabel atau belum.

unique.validator.ts
import { Injectable } from '@nestjs/common';
import {
  ValidationArguments,
  ValidationOptions,
  ValidatorConstraint,
  ValidatorConstraintInterface,
  registerDecorator,
} from 'class-validator';
import { EntityManager } from 'typeorm';

@ValidatorConstraint({ async: true })
@Injectable()
export class UniqueValidator implements ValidatorConstraintInterface {
  constructor(private readonly entityManager: EntityManager) {}
  async validate(value: any, arg?: ValidationArguments): Promise<boolean> {
    const find = { [arg.constraints[1]]: arg.value };
    const user = await this.entityManager
      .getRepository(arg.constraints[0])
      .findOne({
        where: find,
      });
    console.log('er', user);
    if (user === null) {
      return true;
    } else {
      return false;
    }
  }

  defaultMessage(args: ValidationArguments) {
    return `${args.constraints[1]} sudah digunakan`;
  }
}

Pada kode di atas, kita implements class ValidatorConstraintInterface yang memiliki dua method yaitu validate dan defaultMessage

method Validate

Method validate adalah meethod untuk mengecek apakah suatu data pada tabel dengen kriteria tertentu. Jika data tidak ada , maka memiliki return true yang artinya request akan diteruskan ke service, namun return false maka akan di berikan response seperti pada method defaultMessage

method defaultMessage

Method ini memiliki return berupa message kepada client jika ditemukan data yang sama pada tabel dengan kriteria yang direquest.

c.registerDecorator

untuk membuat custom decorator kita harus mendaftarkan decorator pada funtion registerDecorator dari class validator. Berikut kode lengkah nya.

unique.validator.ts
import { Injectable } from '@nestjs/common';
import {
  ValidationArguments,
  ValidationOptions,
  ValidatorConstraint,
  ValidatorConstraintInterface,
  registerDecorator,
} from 'class-validator';
import { EntityManager } from 'typeorm';

@ValidatorConstraint({ async: true })
@Injectable()
export class UniqueValidator implements ValidatorConstraintInterface {
  constructor(private readonly entityManager: EntityManager) {}
  async validate(value: any, arg?: ValidationArguments): Promise<boolean> {
    const find = { [arg.constraints[1]]: arg.value };
    const user = await this.entityManager
      .getRepository(arg.constraints[0])
      .findOne({
        where: find,
      });

    if (user === null) {
      return true;
    } else {
      return false;
    }
  }

  defaultMessage(args: ValidationArguments) {
    return `${args.constraints[1]} sudah digunakan`;
  }
}
export function IsUnique(options: any, validationOptions?: ValidationOptions) {
  return function (object: any, propertyName: string) {
    registerDecorator({
      name: 'IsUnique',
      target: object.constructor,
      constraints: options,
      propertyName: propertyName,
      options: validationOptions,
      validator: UniqueValidator,
      async: true,
    });
  };
}

d. Daftarkan di App Module

class uniqueValidator merupakan class yang dengan decorator @Injectable() maka ketika ingin menggunakan file ini , kita harus deklarasikan di dalam property provider pada file app.module.ts seperti berikut.

import { Module } from '@nestjs/common';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { typeOrmConfig } from './config/typeorm.config';
import { AuthModule } from './app/auth/auth.module';
import { MailModule } from './app/mail/mail.module';
import { ConfigModule } from '@nestjs/config';
import { KategoriModule } from './app/kategori/kategori.module';
import { ProdukModule } from './app/produk/produk.module';
import { BookModule } from './book/book.module';
import { UploadController } from './app/upload/upload.controller';
import { ServeStaticModule } from '@nestjs/serve-static';
import { join } from 'path';
import { KonsumenModule } from './app/konsumen/konsumen.module';
import { UniqueValidator } from './utils/validator/unique.validator';

@Module({
  imports: [
    ServeStaticModule.forRoot({
      rootPath: join(__dirname, '..', 'public'),
    }),

    ConfigModule.forRoot({
      isGlobal: true,
    }),
    TypeOrmModule.forRoot(typeOrmConfig),
    AuthModule,
    MailModule,
    KategoriModule,
    ProdukModule,
    BookModule,
    KonsumenModule,
  ],
  controllers: [AppController, UploadController],
  providers: [AppService, UniqueValidator],
})
export class AppModule {}

e. Implementasi pada DTO

Pada contoh ini, kita akan implementasikan pada konsumen.dto.ts

konsumen.dto.ts
import { OmitType } from '@nestjs/mapped-types';
import { Type } from 'class-transformer';
import {
  IsArray,
  IsEmail,
  IsInt,
  IsNotEmpty,
  IsObject,
  IsOptional,
  IsString,
  MaxLength,
  MinLength,
  ValidateNested,
} from 'class-validator';
import { PageRequestDto } from 'src/utils/dto/page.dto';
import { IsUnique } from 'src/utils/validator/unique.validator';
import { Konsumen } from './konsumen.entity';

export class KonsumenDto {
  @IsInt()
  id: number;

  @IsString()
  @IsNotEmpty()
  nama_konsumen: string;

  @IsString()
  @IsNotEmpty()
  alamat_konsumen: string;

  @IsString()
  @IsEmail()
  @IsUnique([Konsumen, 'email'])
  email: string;

  @IsString()
  @IsNotEmpty()
  @MaxLength(13)
  @MinLength(9)
  nomor_handphone: string;

  @IsObject()
  @IsOptional()
  updated_by: { id: number };
  @IsObject()
  @IsOptional()
  created_by: { id: number };
}

Pada Kode di atas, decorator IsUnique() yang telah kita dibuat akan mengirimkan entity Konsumen sebagai tabel yang akan di cek oleh IsUnique dan field email sebagai field yang akan di cek oleh IsUnique.

Decorator IsUnique() ini dapat digunakan pada semua DTO pada project kita. Tinggal menyesuaikan entity dan nama field nya.

2. Pengujian pada Postman

Alt text

Alt text