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.
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.
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
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.
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
.
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
c.ValidatorConstraint
Selanjutnya kita akan membaut ValidatorConstraint
dan juga class
yang berisi logic untuk mengecek suatu data , apakah sudah ada di tabel atau belum.
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.
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
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.