How to handle TypeORM entity field unique validation error in NestJS?
Solution 1
I have modified my code. I am checking the uniqueness of username/email in the user service (instead of a custom validator) and return an HttpExcetion in case the user is already inserted in the DB.
Solution 2
I can propose 2 different approaches here, the first one catches the constraint violation error locally without additional request, and the second one uses a global error filter, catching such errors in the entire application. I personally use the latter.
Local no-db request solution
No need to make additional database request. You can catch the error violating the unique constraint and throw any HttpException
you want to the client. In users.service.ts
:
public create(newUser: Partial<UserEntity>): Promise<UserEntity> {
return this.usersRepository.save(newUser).catch((e) => {
if (/(email)[\s\S]+(already exists)/.test(e.detail)) {
throw new BadRequestException(
'Account with this email already exists.',
);
}
return e;
});
}
Which will return:
Global error filter solution
Or even create a global QueryErrorFilter:
@Catch(QueryFailedError)
export class QueryErrorFilter extends BaseExceptionFilter {
public catch(exception: any, host: ArgumentsHost): any {
const detail = exception.detail;
if (typeof detail === 'string' && detail.includes('already exists')) {
const messageStart = exception.table.split('_').join(' ') + ' with';
throw new BadRequestException(
exception.detail.replace('Key', messageStart),
);
}
return super.catch(exception, host);
}
}
Then in main.ts
:
async function bootstrap() {
const app = await NestFactory.create(/**/);
/* ... */
const { httpAdapter } = app.get(HttpAdapterHost);
app.useGlobalFilters(new QueryErrorFilter(httpAdapter));
/* ... */
await app.listen(3000);
}
bootstrap();
This will give generic $table entity with ($field)=($value) already exists.
error message. Example:
LuJaks
Updated on June 16, 2022Comments
-
LuJaks almost 2 years
I've set a custom unique validator decorator on my TypeORM entity field email. NestJS has dependency injection, but the service is not injected.
The error is:
TypeError: Cannot read property 'findByEmail' of undefined
Any help on implementing a custom email validator?
user.entity.ts
:@Column() @Validate(CustomEmail, { message: "Title is too short or long!" }) @IsEmail() email: string;
My
CustomEmail
validator isimport {ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments} from "class-validator"; import {UserService} from "./user.service"; @ValidatorConstraint({ name: "customText", async: true }) export class CustomEmail implements ValidatorConstraintInterface { constructor(private userService: UserService) {} async validate(text: string, args: ValidationArguments) { const user = await this.userService.findByEmail(text); return !user; } defaultMessage(args: ValidationArguments) { return "Text ($value) is too short or too long!"; } }
I know I could set
unique
in theColumn
options@Column({ unique: true })
but this throws a mysql error and the
ExceptionsHandler
that crashes my app, so I can't handle it myself...Thankx!
-
johnkork almost 3 yearsSeems like a good solution imho, especially the global filter. Got it working using exception = new BadRequestException(...) instead of a throw, throwing it right away did cause a loss of every single exceptions returned to the client.
-
Ilesanmi John almost 2 yearsYou can perform that check using pipes or a middleware(not recommended if you intend to reuse app wide) instead of the user service to keep to S(Single Responsibility Principle) in the SOLID design principle.