DEV Community

dmitryvz
dmitryvz

Posted on

Using Yup to validate user input in a NestJS project

This article assumes that you are familiar with NestJS framework.

The Importance of Validation
Validation is a crucial step in ensuring the integrity and security of your application. While NestJS documentation offers the class-validator library for validation, it can sometimes be challenging to work with. Lets explore an alternative validation approach using Yup in a NestJS project.

Using class-validator
In a typical NestJS project, you might define your DTOs (Data Transfer Objects) with class-validator decorators to perform validation. In my project users can submit posts with title and variable amount of teams. A team has one field - title. In accordance with Nestjs docs I created a DTO for post data and added validation to it:

class Team {
  @IsDefined({message: 'Bad request'})
  @IsString({message: 'Bad request'})
  @MinLength(TITLE_MIN_LENGTH, {
    message: 'Too short',
  })
  @MaxLength(TITLE_MAX_LENGTH, {
    message: 'Too long',
  })
  name: string;
}

class PostBody {
  @IsDefined({message: 'Bad request'})
  @IsString({message: 'Bad request'})
  @MinLength(TITLE_MIN_LENGTH, {
    message: 'Too short',
  })
  @MaxLength(TITLE_MAX_LENGTH, {
    message: 'Too long',
  })
  title: string;

  @IsDefined({message: 'Bad request'})
  @IsArray({message: 'Bad request'})
  @ArrayMinSize(MIN_TEAMS)
  @ArrayMaxSize(MAX_TEAMS)
  @ValidateNested()
  @Type(() => Team)
  teams: Team[];
}
Enter fullscreen mode Exit fullscreen mode

Pretty hard to find where the real DTO is, huh?

My primary concern was that the ArrayMinSize and ArrayMaxSize validators were not functioning correctly. Users could submit any number of teams, and the DTO would still pass validation. This is a known issue with class-validator, and although workarounds exist, none of them seemed optimal.

Introducing Yup
After conducting some research I decided to switch to a library that works for all of my use cases out of box. I have looked at most popular validation libraries and found out that Yup covers all my needs:

  • Validates my DTOs.
  • Easy schema definition syntax.
  • Ability to incorporate error messages directly into validation rules.

Yup functions similarly to class-validator. It requires you to define validation rule schemas, allowing to separate the DTO from validation. The DTO is now a plain JavaScript object:

class PostBody {
  title: string;
  teams: Team[];
}
Enter fullscreen mode Exit fullscreen mode

Validation rules can be defined as constants and can be reused across various validation scenarios. Lets define rule for validating title:

const titleSchema = string()
  .strict()
  .typeError('Bad request')
  .trim()
  .min(TITLE_MIN_LENGTH, `Too short`)
  .max(TITLE_MAX_LENGTH, `Too long`)
Enter fullscreen mode Exit fullscreen mode

And now to the main point - validation for an array of objects:

const teamsSchema = array()
  .strict()
  .typeError('Bad request')
  .min(MIN_TEAMS, 'Bad request')
  .max(MAX_TEAMS, 'Bad request')
  .of(
    object({
      name: titleSchema,
    }),
  )
Enter fullscreen mode Exit fullscreen mode

Schema for validating the entire post:

const postSchema = object({
  title: titleSchema,
  teams: teamsSchema,
})
Enter fullscreen mode Exit fullscreen mode

Note the reuse of titleSchema in postSchema and in teamsSchema. DRY in action.

Declaring validation rules as constants enables us to create similar schemas without the need to redundantly redeclare the rules. For instance, we can define a "post create" schema that accepts all fields and a "post update" schema that accepts only the title field.

To integrate Yup validation into a NestJS project, we'll need a custom validation pipe:

class YupValidationPipe implements PipeTransform {
  constructor(private schema: ObjectSchema<any>) {}

  transform(value: any) {
    return this.schema.validateSync(value);
  }
}
Enter fullscreen mode Exit fullscreen mode

Now we can use the YupValidationPipe in a controller action to validate incoming data easily:

async createPost(
  @GetUser() user: User, 
  @Body(new YupValidationPipe(postSchema)) post: PostBody
) { ... }
Enter fullscreen mode Exit fullscreen mode

Conclusion
While initially seeking a library solely for required validation, my transition to Yup yielded unexpected advantages:

  • Cleaner and more concise DTO code.
  • Rule reusability.
  • Convenient error message handling within validation rules, facilitating user-friendly error displays (an uncommon feature in many libraries).

Of course, integrating Yup into my NestJS project required some additional effort, but it ultimately proved to be a more efficient solution compared to troubleshooting issues with class-validator.

Visit clapduel.com to see the code in action.

Top comments (0)