How to handle TypeORM entity field unique validation error in NestJS?

12,199

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:

Screenshot of error from Insomnia (MacOS App)

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:

enter image description here

Share:
12,199
LuJaks
Author by

LuJaks

Updated on June 16, 2022

Comments

  • LuJaks
    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 is

    import {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 the Column 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
    johnkork almost 3 years
    Seems 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
    Ilesanmi John almost 2 years
    You 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.