Membuat Custom Validation dengan Database di NestJS

Membuat Custom Validation dengan Database di NestJS

Custom Validation dengan Database di NestJS menggunakan library class-validator

Pada artikel kali ini, saya akan mencoba menjelaskan/membuat custom validation dengan database di NestJS

Pendahuluan

Berdasarkan halaman dokumentasi NestJS, NestJS adalah sebuah framework untuk membangun aplikasi backend/server-side yang scalable dan efisien. NestJS juga dibangun dan sepenuhnya mendukung Typescript.

Framework NestJS sendiri sudah banyak menyediakan cara untuk melakukan validasi request data, seperti menggunakan Pipes . Tetapi kita juga bisa menggunakan library class-validator melalui ValidationPipe.

ValidationPipe menyediakan pendekatan yang nyaman dan mudah untuk menerapkan aturan-aturan validasi dari request data, dimana nantinya aturan-aturan validasi ini nantinya dapat diterapkan di dalam class atau DTO dari setiap module.

Validasi dengan menggunakan library class-validator ini disarankan juga oleh NestJS, seperti yang ada pada dokumentasi NestJS.

Membuat Custom Validation

class-validator sendiri adalah library yang powerful dan sudah banyak digunakan. class-validator menyediakan banyak decorator untuk validasi. Namun, class-validator hanya menyediakan untuk validasi dasar saja seperti mengecek apakah string, apakah input kosong dan lainnya. Untuk validasi seperti mengecek data ke database class-validator tidak menyediakan secara default, tetapi class-validator juga menyediakan untuk kita dapat membuat sebuah custom validation.

Untuk menggunakan class-validator kita bisa menginstallnya terlebih dahulu dengan menggunakan perintah.

npm i --save class-validator class-transformer

Setelah itu, kita bisa menggunakan fungsi useContainer dari class-validator yang fungsinya adalah untuk melakukan setting container agar dapat digunakan oleh library class-validator dan juga untuk melakukan NestJS depedency injection.

Kita bisa menggunakannya di dalam file main.ts.

import 'dotenv/config';
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);

  const port = process.env.PORT || 3001;

  app.useGlobalPipes(new ValidationPipe({ transform: true }));

  // Tambahkan ini di file main.ts
  useContainer(app.select(AppModule), { fallbackOnErrors: true });

  await app.listen(port, () => {
    console.log(`Listen on port ${port}`);
  });
}
bootstrap();

Selanjutnya kita bisa membuat ValidatorConstraint dan juga class yang isinya adalah logic untuk validasi yang akan kita buat.

Kita bisa buat sebuah folder dengan nama validation dan buat file dengan nama (disesuaikan) UserExists.ts Untuk penamaan file bisa disesuaikan dengan kebutuhan, karena saya disini akan membuat validasi untuk mengecek apakah user terdaftar atau tidak maka saya namai dengan UserExists.

import { Injectable } from '@nestjs/common';
import {
  ValidatorConstraint,
  ValidatorConstraintInterface,
  ValidationOptions,
  registerDecorator,
} from 'class-validator';
import { UsersService } from 'src/users/users.service';

@ValidatorConstraint({ name: 'UserExists', async: true })
@Injectable()
export class UserExistsValidation implements ValidatorConstraintInterface {
  constructor(private usersService: UsersService) {}

  async validate(value: number): Promise<boolean> {
    return this.usersService.getUsersById(value).then((data) => {
      if (data !== null) {
        return true;
      } else {
        return false;
      }
    });
  }

  defaultMessage() {
    return `users doesn't exists in table users`;
  }
}

Class diatas berfungsi untuk mencari data user ke database sesuai dengan id yang diinputkan (melalui method getUsersById yang ada pada class userService). Method validate harus mereturn sebuah tipe data boolean.

Disini kita menggunakan decorator @Injectable() untuk kita bisa melakukan depedency injection, seperti yang kita lihat kita melakukan injection depedency ke dalam constructor dari class UserExistsValidation.

Setelah kita membuat class tersebut, kita bisa mendeklarasikan/menggunakan pada providers di module yang sesuai. Karena disini saya akan menggunakannya di users, maka saya akan mendeklarasikannya di module users atau di file users.module.ts

import { Module } from '@nestjs/common';
import { SequelizeModule } from '@nestjs/sequelize';
import { UsersService } from './users.service';
import { users } from 'models';
import { UsersController } from './users.controller';
import { UserExistsValidation } from './validation/userExists';
import { AuthModule } from 'src/auth/auth.module';

@Module({
  imports: [SequelizeModule.forFeature([users])],
  providers: [UsersService, UserExistsValidation], // Deklarasi Injectable Class
  controllers: [UsersController],
  exports: [UsersService],
})
export class UsersModule {}

Setelah itu kita bisa gunakan validasi yang tadi kita buat di dalam sebuah class/DTO mana saja. Disini contohnya saya akan menggunakannya di dalam CreateCustomerDto.

Cara menggunakannya adalah dengan menggunakan decorator @Validate(nama class) atau disini @Validate(UserExistsValidation)

import { IsNotEmpty, Validate } from 'class-validator';
import { UserExistsValidation } from 'src/users/validation/userExists';

export class CreateCustomerDto {
  @IsNotEmpty()
  firstname: string;

  @IsNotEmpty()
  lastname: string;

  @IsNotEmpty()
  @Validate(UserExistsValidation) // Menggunakan validasi
  user_id: number;
}

Jika user_id yang kita input tidak ada di database/tableusers maka akan mereturn error dengan pesan: "users doesn't exists in table users".

Nah dari sini kita sudah bisa memakai custom validation yang sudah kita buat sendiri di class/DTO manapun di dalam project kita.

Kita juga bisa membuat decorator sendiri, alih-alih kita harus memanggil @Validate(UserExistsValidation) kita bisa membuatnya menjadi @IsUserExists

Membuat Decorator Custom Sendiri

Cara membuatnya adalah hanya dengan cara menggunakan decorator factory atau dengan menggunakan fungsi registerDecorator() .

Kita buat fungsi sesuai dengan nama decorator yang akan kita buat di dalam file yang sama ketika kita membuat class untuk custom validation, disini saya akan membuat decorator IsUserExist maka saya akan membuat fungsi dengan nama IsUserExist .

import { Injectable } from '@nestjs/common';
import {
  ValidatorConstraint,
  ValidatorConstraintInterface,
  ValidationOptions,
  registerDecorator,
} from 'class-validator';
import { UsersService } from 'src/users/users.service';

@ValidatorConstraint({ name: 'UserExists', async: true })
@Injectable()
export class UserExistsValidation implements ValidatorConstraintInterface {
  constructor(private usersService: UsersService) {}

  async validate(value: number): Promise<boolean> {
    return this.usersService.getUsersById(value).then((data) => {
      if (data !== null) {
        return true;
      } else {
        return false;
      }
    });
  }

  defaultMessage() {
    return `users doesn't exists in table users`;
  }
}

// Register The Decorator
export function IsUserExist(
  property: string,
  validationOptions?: ValidationOptions,
) {
  return function (object: any, propertyName: string) {
    registerDecorator({
      name: 'UserExists',
      target: object.constructor,
      propertyName: propertyName,
      constraints: [property],
      options: validationOptions,
      validator: UserExistsValidation,
    });
  };
}

Decorator IsUserExist sendiri menerima dua parameter, satu adalah untuk property dan juga validationOptions yang dapat kita isi seperti custom message dan lainnya.

Setelah itu kita bisa ganti cara pemanggilan validasi dari @Validate(UserExistsValidation) menjadi hanya @IsUserExists di dalam class/DTO kita.

import { IsNotEmpty } from 'class-validator';
import { IsUserExist } from 'src/users/validation/userExists';

export class CreateCustomerDto {
  @IsNotEmpty()
  firstname: string;

  @IsNotEmpty()
  lastname: string;

  @IsNotEmpty()
  // Memanggil decorator, dan menambahkan custom message
  @IsUserExist('user_id', {
    message: `user tidak ditemukan di dalam database`,
  })
  user_id: number;
}

Decorator @IsUserExist kita mengisi argument seperti property yang diisi dengan 'user_id' dan juga validationOptions yaitu custom message: "user tidak ditemukan di dalam database".


Begitulah kira-kira cara untuk membuat Custom Validation sekaligus juga membuat Custom Decorator. Untuk lebih jelasnya kita bisa lihat pada referensi dibawah.