NestJS + Prisma: Custom "Exist" Validation

When building APIs with NestJS and Prisma, you often need to validate whether a record exists in your database before processing a request. While NestJS provides powerful validation decorators, there's no built-in way to check if a database record exists.
In this guide, I'll show you how to create a custom validation decorator that integrates seamlessly with NestJS's validation system to check if records exist in your Prisma database.
Why Custom Validation?
Default validation decorators like @IsEmail() or @IsString() work great for format validation, but they can't check your database. Custom validators bridge this gap.
- Validate foreign key relationships before saving
- Ensure referential integrity at the API level
- Provide clear, user-friendly error messages
The Implementation
We'll create a decorator called @Exists() that can be applied to any DTO property to validate that the provided ID exists in the specified database table.
Key Features:
🚀Complete Implementation
Step 1: Create the Validator
// exists.validator.ts
import { Injectable } from '@nestjs/common';
import {
ValidatorConstraint,
ValidatorConstraintInterface,
ValidationArguments
} from 'class-validator';
import { PrismaService } from '../prisma/prisma.service';
@ValidatorConstraint({ name: 'Exists', async: true })
@Injectable()
export class ExistsValidator implements ValidatorConstraintInterface {
constructor(private prisma: PrismaService) {}
async validate(value: any, args: ValidationArguments): Promise<boolean> {
const [model, field = 'id'] = args.constraints;
if (!value) return false;
const record = await this.prisma[model].findFirst({
where: { [field]: value }
});
return !!record;
}
defaultMessage(args: ValidationArguments): string {
const [model, field = 'id'] = args.constraints;
return `${model} with ${field} ${args.value} does not exist`;
}
}Step 2: Create the Decorator
// exists.decorator.ts
import { registerDecorator, ValidationOptions } from 'class-validator';
import { ExistsValidator } from './exists.validator';
export function Exists(
model: string,
field?: string,
validationOptions?: ValidationOptions,
) {
return function (object: object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
constraints: [model, field],
validator: ExistsValidator,
});
};
}Step 3: Register in Module
// app.module.ts
import { Module } from '@nestjs/common';
import { ExistsValidator } from './validators/exists.validator';
import { PrismaModule } from './prisma/prisma.module';
@Module({
imports: [PrismaModule],
providers: [ExistsValidator],
// ... other module configuration
})
export class AppModule {}Step 4: Use in DTOs
// create-post.dto.ts
import { IsNotEmpty, IsString } from 'class-validator';
import { Exists } from '../validators/exists.decorator';
export class CreatePostDto {
@IsString()
@IsNotEmpty()
title: string;
@IsString()
@IsNotEmpty()
content: string;
@Exists('user', 'id', { message: 'User does not exist' })
@IsNotEmpty()
userId: number;
@Exists('category', 'id', { message: 'Category does not exist' })
@IsNotEmpty()
categoryId: number;
}Advanced Usage Examples
// Examples of different field validations
export class ExampleDto {
// Check if user exists by ID (default)
@Exists('user')
userId: number;
// Check if user exists by email
@Exists('user', 'email')
userEmail: string;
// Check if product exists by SKU
@Exists('product', 'sku', { message: 'Product SKU not found' })
productSku: string;
}